--- name: flutter-soloud-volume-pan version: 1 description: 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. --- 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 ```dart import 'package:flutter_soloud/flutter_soloud.dart'; Future 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: ```dart 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: ```sh 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).