# Flutter Soloud Synthesis

> Teaches how to generate audio without assets in flutter_soloud using loadWaveform() with the 9 WaveForm oscillators, runtime setWaveform*() tweaks, speechText() (built-in robotic TTS that creates and plays a source), and SoLoudTools.createNotes(). Use when the user asks for a tone generator, beeps/alerts, a keyboard/instrument, synthesized sound effects, or text-to-speech without audio files.

- Skill: `alnitak/flutter-soloud-synthesis` (Agent Skill)
- Install (CLI): `npx skillmds@latest add alnitak/flutter-soloud-synthesis`
- Raw SKILL.md: https://api.skillmd.com/api/skills/alnitak/flutter-soloud-synthesis/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: alnitak (https://skillmd.com/u/alnitak)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/alnitak/flutter-soloud-synthesis

---


# Synthesis: waveforms and speech without assets

flutter_soloud can synthesize audio with zero files. `SoLoud.instance.loadWaveform()` creates an `AudioSource` from an oscillator (not a stream to "set" — there is no `setSource`/`setUrl` like audioplayers; you create a source, then `play()` it). The returned `AudioSource` is reused and disposed exactly like one loaded from an asset. Runtime tweaks (`setWaveform*`) act on the **source**, so they affect every playing instance of it.

## Minimal example

```dart
import 'package:flutter_soloud/flutter_soloud.dart';

Future<void> beep() async {
  if (!SoLoud.instance.isInitialized) {
    await SoLoud.instance.init();
  }

  final tone = await SoLoud.instance.loadWaveform(
    WaveForm.sin, // oscillator type
    false,        // superWave
    1.0,          // scale
    0.0,          // detune
  );
  SoLoud.instance.setWaveformFreq(tone, 440); // Hz

  final handle = SoLoud.instance.play(tone); // sync, returns SoundHandle

  // ... later
  await SoLoud.instance.stop(handle);
  await SoLoud.instance.disposeSource(tone); // required: it's an AudioSource
}
```

## The API shape

### Loading

```dart
Future<AudioSource> loadWaveform(
  WaveForm waveform,
  bool superWave,
  double scale,
  double detune,
)
```

Positional args, all required. `superWave` combines several detuned oscillators for a fatter sound; `scale` and `detune` only matter when `superWave` is true. Throws `SoLoudNotInitializedException` before `init()`.

### WaveForm enum (exact names, `lib/src/enums.dart`)

`square`, `saw`, `sin`, `triangle`, `bounce` (abs(sin)), `jaws` (quarter sine, rest quiet), `humps` (half sine, rest quiet), `fSquare` ("Fourier" square, less noisy), `fSaw` ("Fourier" saw, less noisy).

Note the casing: `fSquare` / `fSaw` — not `fsquare`/`fsaw` (the upstream docs page lists them lowercase; the code is authoritative).

### Runtime tweaks (all `void`, all take the `AudioSource`, not the handle)

| Method | Effect |
| --- | --- |
| `setWaveform(AudioSource sound, WaveForm newWaveform)` | switch oscillator type |
| `setWaveformFreq(AudioSource sound, double newFrequency)` | frequency in Hz |
| `setWaveformSuperWave(AudioSource sound, bool superwave)` | toggle super wave |
| `setWaveformScale(AudioSource sound, double newScale)` | super-wave scale |
| `setWaveformDetune(AudioSource sound, double newDetune)` | super-wave detune |

These change live playback — call them on slider changes; no need to reload or stop.

### Speech

```dart
AudioSource speechText(String textToSpeech)
```

Synchronous, and it **creates AND immediately plays** the source — don't call `play()` on it. The voice is SoLoud's own built-in formant synth: deliberately retro/robotic, not the platform's system TTS (no `flutter_tts`-style voice/rate/pitch controls, no per-platform quality differences).

### Note bank helper

```dart
static Future<List<AudioSource>> SoLoudTools.createNotes({
  int octave = 3,               // asserted 0..4
  WaveForm waveForm = WaveForm.sin,
  bool superwave = true,
})
```

Returns 12 `AudioSource`s (the chromatic notes starting at `55 Hz * 2^octave`) ready to `play()`. Each is a separate source — dispose all 12 when done. Typical keyboard pattern:

```dart
final notes = await SoLoudTools.createNotes(octave: 2);
SoLoud.instance.play(notes[4]);                       // key pressed
await SoLoud.instance.stop(notes[4].handles.first);   // key released
```

## Recipe: tone generator / instrument

Model on `example/lib/waveform/waveform.dart`:

1. `await SoLoud.instance.init()` once; `SoLoud.instance.deinit()` in the owning widget's `dispose()`.
2. Create the source with `loadWaveform`, then `play()` it and keep both `AudioSource` and `SoundHandle` in state.
3. Wire UI controls straight to `setWaveform*` on the source while it plays.
4. On stop: `await SoLoud.instance.stop(handle)`, and dispose the source when it's no longer needed (`await SoLoud.instance.disposeSource(source)`).

## Traps

- **`play()` is synchronous and returns `SoundHandle`** — no `await`, and unlike just_audio there is no `setUrl`/`AudioPlayer` object; the `SoLoud` singleton is the whole player.
- **Every synthesis call requires `init()` first** and throws `SoLoudNotInitializedException` otherwise. `init()` is async; `isInitialized` lets you check synchronously.
- **Waveform/speech sources are `AudioSource`s like file-based ones.** They are NOT auto-freed when playback ends: call `disposeSource(source)` (or `disposeAllSources()`), or let `deinit()` clean everything up. Creating a new `loadWaveform` per beep without disposing leaks.
- **`speechText()` plays immediately.** Calling `play()` on its returned source is wrong. Dispose it like any other source when finished (see `example/tests/tests/speech_text.dart`).
- **`setWaveformScale`/`setWaveformDetune` are no-ops in practice unless super wave is on** (`loadWaveform(..., superWave: true, ...)` or `setWaveformSuperWave(sound, true)`). The docs' own demo disables those sliders when super wave is off.
- **`setWaveform*` targets the source, not a handle** — it retunes all currently playing instances of that source. For per-note pitch, create one source per note (`SoLoudTools.createNotes`) instead of retuning one shared source.
- **`createNotes` asserts `octave` in 0..4** and each call creates 12 sources — dispose them all, and don't call it repeatedly per key press; build the bank once.
- **No envelopes/ADSR built in.** Raw oscillators drone until stopped. Fade or stop handles yourself (`stop(handle)`, or `fadeVolume(handle, 0, duration)` then stop) to avoid clicks and endless tones.
- **Square/saw are loud and harsh** — consider lowering `play(..., volume: ...)` or using the `fSquare`/`fSaw` filtered variants for smoother output.

## More depth

- Demo: `example/lib/waveform/waveform.dart` — full tone generator UI (superWave/scale/detune sliders, all 9 `WaveForm` buttons).
- Tests with idiomatic usage: `example/tests/tests/create_notes.dart`, `example/tests/tests/speech_text.dart`, `example/tests/tests/waveform_controls.dart`.
- Real signatures: `loadWaveform`/`setWaveform*`/`speechText` at `lib/src/soloud.dart:2076-2195`; `WaveForm` at `lib/src/enums.dart:290-317`; `SoLoudTools.createNotes` in `lib/src/tools/soloud_tools.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).

