Volume and pan in flutter_soloud are applied to handles (one per playing
voice), not to the loaded AudioSource. Loading a sound gives an AudioSource;
each play() returns a SoundHandle you control afterwards. Global volume is
engine-wide and multiplies with per-handle volume. Fades, oscillations, and
scheduled pause/stop run on the engine's internal fader clock, not on Dart
timers.
Minimal example
import 'package:flutter_soloud/flutter_soloud.dart';
Future<void> main() async {
await SoLoud.instance.init();
final source = await SoLoud.instance.loadAsset('assets/sfx.ogg');
// volume and pan can be set at play time...
final handle = SoLoud.instance.play(source, volume: 0.8, pan: -0.5);
// ...or changed afterwards on the handle.
SoLoud.instance.setVolume(handle, 0.6);
SoLoud.instance.setPan(handle, 0); // 0 = centered
// Fade out over 2 s, then stop 2 s from now.
SoLoud.instance.fadeVolume(handle, 0, const Duration(seconds: 2));
SoLoud.instance.scheduleStop(handle, const Duration(seconds: 2));
SoLoud.instance.setGlobalVolume(0.9); // master volume, all sounds
}
The API shape
All members of SoLoud.instance. Everything throws
SoLoudNotInitializedException if init() has not completed.
Per-handle volume / pan:
void setVolume(SoundHandle handle, double volume) / double getVolume(handle) —
0.0 muted, 1.0 full. Per-voice gain; multiplies with global volume.
void setPan(SoundHandle handle, double pan) / double getPan(handle) —
-1.0 = full left, 0.0 = CENTER, 1.0 = full right. Diverges from
audioplayers (whose setBalance uses the same range) and from just_audio
(no pan at all), but trips up models assuming 0..1: setPan(handle, 0.5) is
half-right, not "half volume". Debug builds assert the range; release builds
clamp to -1..1.
void setPanAbsolute(SoundHandle handle, double panLeft, double panRight) —
sets the L/R channel volumes directly (each -1..1). Bypasses the pan law.
Does not affect what getPan returns.
Global:
void setGlobalVolume(double volume) / double getGlobalVolume() —
0.0..1.0, affects every voice.
Fades (signature: (handle, to, Duration time), global variant omits the
handle):
void fadeVolume(SoundHandle handle, double to, Duration time)
void fadePan(SoundHandle handle, double to, Duration time)
void fadeRelativePlaySpeed(SoundHandle handle, double to, Duration time) —
ramps playback speed (and pitch, since SoLoud resamples).
void fadeGlobalVolume(double to, Duration time)
Oscillators (signature: (handle, from, to, Duration time) — time is the
full period of one from→to→from cycle):
void oscillateVolume(SoundHandle handle, double from, double to, Duration time)
void oscillatePan(SoundHandle handle, double from, double to, Duration time)
void oscillateRelativePlaySpeed(SoundHandle handle, double from, double to, Duration time)
void oscillateGlobalVolume(double from, double to, Duration time)
Scheduled actions (engine-clock, no Dart Future.delayed needed):
void schedulePause(SoundHandle handle, Duration time)
void scheduleStop(SoundHandle handle, Duration time)
Metering:
double getApproximateVolume(int channel) — per-output-channel post-mix
level for VU meters (channel 0 = left, 1 = right in stereo).
Only returns non-zero when visualization is enabled:
SoLoud.instance.setVisualizationEnabled(true) first.
Returns 0 for an invalid channel index.
Traps
Simultaneous sounds sum and clip. Two voices at volume 1.0 can exceed
full scale and distort; lowering volumes per-sound is a losing battle. The
intended fix is the global limiter filter, which caps the mixed output:
await SoLoud.instance.init();
SoLoud.instance.filters.limiterFilter.activate();
// Optionally lower the ceiling (dB, should stay < 0):
SoLoud.instance.filters.limiterFilter.outputCeiling.value = -3;
See the sibling skill flutter_soloud-filters for filter details.
Fades/oscillations keep running on stopped handles. If a handle is
stopped (or scheduleStop fires) mid-fade, the fader is not cancelled; when
the same sound is played again and the handle id is recycled, the old ramp
can snap the new voice's volume/pan. Reset the parameter explicitly
(setVolume/setPan) when starting a new voice if you previously faded.
getVolume/getPan/getGlobalVolume round-trip through float.
Reading back 0.8 can return 0.800000042353. Never compare with ==.
Handles die. After a sound ends or is stopped, the handle is invalid.
setVolume on a dead handle fails silently on the native side; check
getIsValidVoiceHandle(handle) if unsure. Don't cache handles across
stop()/scheduleStop().
getApproximateVolume returns 0 forever if visualization was never
enabled — it reads the same analysis buffers as the waveform/FFT feature.
setRelativePlaySpeed(handle, 0) is undefined behavior (likely crash);
the lower bound is silently clamped to 0.05. Same applies to
fadeRelativePlaySpeed / oscillateRelativePlaySpeed targets.
Max voice count is 16 by default. Extra play() calls steal the oldest
voice of the same sound (or are dropped with a log warning — no exception),
so a fade targeting the stolen handle does nothing. Raise with
setMaxActiveVoiceCount if needed.
Fades are synchronous calls, not Futures. fadeVolume returns
immediately; if you need to act when the fade finishes, await your own
Future.delayed with the same duration.
More depth
- Limiting and other filters: sibling skill
flutter_soloud-filters; demo
example/lib/filters/limiter.dart.
- Bus-level volume (group many sounds under one fader):
example/lib/mixing_bus/mixing_bus.dart.
- Executable usage references:
example/tests/tests/volume_controls.dart,
example/tests/tests/pan.dart, example/tests/tests/advanced_pan.dart.
Keeping this skill current
This skill ships inside the flutter_soloud package, so upgrading flutter_soloud can carry a newer revision of it than the copy installed in the project. To check, run:
dart run flutter_soloud:skills --check
It reports the installed and bundled skill versions and exits non-zero when an update is available. Offer to update with dart run flutter_soloud:skills (which touches only the skills, never pubspec or build files).
1---2name: flutter-soloud-volume-pan3description: Teaches volume, pan, fade, oscillator, and scheduled-stop control in the flutter_soloud package, including the handle-based model, setPanAbsolute, getApproximateVolume for VU meters, and the limiter filter as the anti-clipping tool. Use when the user asks to change a sound's volume, pan a sound left/right, fade or oscillate audio parameters, build a VU meter, or stop clipping when many sounds play at once.4---56Volume and pan in flutter_soloud are applied to **handles** (one per playing7voice), not to the loaded `AudioSource`. Loading a sound gives an `AudioSource`;8each `play()` returns a `SoundHandle` you control afterwards. Global volume is9engine-wide and multiplies with per-handle volume. Fades, oscillations, and10scheduled pause/stop run on the engine's internal fader clock, not on Dart11timers.1213## Minimal example1415```dart16import 'package:flutter_soloud/flutter_soloud.dart';1718Future<void> main() async {19 await SoLoud.instance.init();2021 final source = await SoLoud.instance.loadAsset('assets/sfx.ogg');2223 // volume and pan can be set at play time...24 final handle = SoLoud.instance.play(source, volume: 0.8, pan: -0.5);2526 // ...or changed afterwards on the handle.27 SoLoud.instance.setVolume(handle, 0.6);28 SoLoud.instance.setPan(handle, 0); // 0 = centered2930 // Fade out over 2 s, then stop 2 s from now.31 SoLoud.instance.fadeVolume(handle, 0, const Duration(seconds: 2));32 SoLoud.instance.scheduleStop(handle, const Duration(seconds: 2));3334 SoLoud.instance.setGlobalVolume(0.9); // master volume, all sounds35}36```3738## The API shape3940All members of `SoLoud.instance`. Everything throws41`SoLoudNotInitializedException` if `init()` has not completed.4243Per-handle volume / pan:4445- `void setVolume(SoundHandle handle, double volume)` / `double getVolume(handle)` —46 `0.0` muted, `1.0` full. Per-voice gain; multiplies with global volume.47- `void setPan(SoundHandle handle, double pan)` / `double getPan(handle)` —48 **`-1.0` = full left, `0.0` = CENTER, `1.0` = full right.** Diverges from49 audioplayers (whose `setBalance` uses the same range) and from just_audio50 (no pan at all), but trips up models assuming 0..1: `setPan(handle, 0.5)` is51 half-right, not "half volume". Debug builds assert the range; release builds52 clamp to -1..1.53- `void setPanAbsolute(SoundHandle handle, double panLeft, double panRight)` —54 sets the L/R channel volumes directly (each -1..1). Bypasses the pan law.55 **Does not affect what `getPan` returns.**5657Global:5859- `void setGlobalVolume(double volume)` / `double getGlobalVolume()` —60 `0.0`..`1.0`, affects every voice.6162Fades (signature: `(handle, to, Duration time)`, global variant omits the63handle):6465- `void fadeVolume(SoundHandle handle, double to, Duration time)`66- `void fadePan(SoundHandle handle, double to, Duration time)`67- `void fadeRelativePlaySpeed(SoundHandle handle, double to, Duration time)` —68 ramps playback speed (and pitch, since SoLoud resamples).69- `void fadeGlobalVolume(double to, Duration time)`7071Oscillators (signature: `(handle, from, to, Duration time)` — `time` is the72full period of one from→to→from cycle):7374- `void oscillateVolume(SoundHandle handle, double from, double to, Duration time)`75- `void oscillatePan(SoundHandle handle, double from, double to, Duration time)`76- `void oscillateRelativePlaySpeed(SoundHandle handle, double from, double to, Duration time)`77- `void oscillateGlobalVolume(double from, double to, Duration time)`7879Scheduled actions (engine-clock, no Dart `Future.delayed` needed):8081- `void schedulePause(SoundHandle handle, Duration time)`82- `void scheduleStop(SoundHandle handle, Duration time)`8384Metering:8586- `double getApproximateVolume(int channel)` — per-output-channel post-mix87 level for VU meters (channel `0` = left, `1` = right in stereo).88 **Only returns non-zero when visualization is enabled**:89 `SoLoud.instance.setVisualizationEnabled(true)` first.90 Returns `0` for an invalid channel index.9192## Traps9394- **Simultaneous sounds sum and clip.** Two voices at volume 1.0 can exceed95 full scale and distort; lowering volumes per-sound is a losing battle. The96 intended fix is the global limiter filter, which caps the mixed output:9798 ```dart99 await SoLoud.instance.init();100 SoLoud.instance.filters.limiterFilter.activate();101 // Optionally lower the ceiling (dB, should stay < 0):102 SoLoud.instance.filters.limiterFilter.outputCeiling.value = -3;103 ```104105 See the sibling skill **flutter_soloud-filters** for filter details.106- **Fades/oscillations keep running on stopped handles.** If a handle is107 stopped (or `scheduleStop` fires) mid-fade, the fader is not cancelled; when108 the same sound is played again and the handle id is recycled, the old ramp109 can snap the new voice's volume/pan. Reset the parameter explicitly110 (`setVolume`/`setPan`) when starting a new voice if you previously faded.111- **`getVolume`/`getPan`/`getGlobalVolume` round-trip through float.**112 Reading back `0.8` can return `0.800000042353`. Never compare with `==`.113- **Handles die.** After a sound ends or is stopped, the handle is invalid.114 `setVolume` on a dead handle fails silently on the native side; check115 `getIsValidVoiceHandle(handle)` if unsure. Don't cache handles across116 `stop()`/`scheduleStop()`.117- **`getApproximateVolume` returns 0 forever** if visualization was never118 enabled — it reads the same analysis buffers as the waveform/FFT feature.119- **`setRelativePlaySpeed(handle, 0)`** is undefined behavior (likely crash);120 the lower bound is silently clamped to `0.05`. Same applies to121 `fadeRelativePlaySpeed` / `oscillateRelativePlaySpeed` targets.122- **Max voice count is 16 by default.** Extra `play()` calls steal the oldest123 voice of the same sound (or are dropped with a log warning — no exception),124 so a fade targeting the stolen handle does nothing. Raise with125 `setMaxActiveVoiceCount` if needed.126- **Fades are synchronous calls, not Futures.** `fadeVolume` returns127 immediately; if you need to act when the fade finishes, await your own128 `Future.delayed` with the same duration.129130## More depth131132- Limiting and other filters: sibling skill `flutter_soloud-filters`; demo133 `example/lib/filters/limiter.dart`.134- Bus-level volume (group many sounds under one fader):135 `example/lib/mixing_bus/mixing_bus.dart`.136- Executable usage references: `example/tests/tests/volume_controls.dart`,137 `example/tests/tests/pan.dart`, `example/tests/tests/advanced_pan.dart`.138139## Keeping this skill current140141This skill ships inside the flutter_soloud package, so upgrading flutter_soloud can carry a newer revision of it than the copy installed in the project. To check, run:142143```sh144dart run flutter_soloud:skills --check145```146147It reports the installed and bundled skill versions and exits non-zero when an update is available. Offer to update with `dart run flutter_soloud:skills` (which touches only the skills, never pubspec or build files).