# sound

> Drop `.mp3`, `.wav`, `.ogg` or `.m4a` files into `model/sounds/` and play them with `Sound.Play(Sounds.Flap)`. The name is a compile-checked member of your app's `Sounds` vocabulary, so a typo is an error rather than silence. `Sound.Loop` starts background audio and `Sound.Stop` ends it.

<!-- id: ui-sound · area: ui · stability: preview · html: https://osysharp.com/reference/ui/sound/ -->

## Summary        {#summary}
A sound is a **file in your app**, not data. Put a `.wav` in `model/sounds/` and it becomes part of your app's
vocabulary:

```text
model/
  sounds/
    blip.wav
    game_over.mp3
    valley.ogg
```

`blip.wav` is now playable as `Sound.Play(Sounds.Blip)`. There is nothing to register, nothing to import, and no
path to spell.

Sounds are the **fourth kind of asset** an app ships, and they divide by what the file *is*:

| you have | put it in | reach it with |
|---|---|---|
| a single-colour glyph that should follow your text | `model/icons/` | `Icon(Icons.Search)` |
| a vector illustration, logo or background | `model/art/` | `Svg(Art.Hexgrid)` |
| a bitmap — a wall texture, a sprite sheet, a photograph | `model/textures/` | `Draw.Image(wall, …)` |
| **audio** — an effect, a jingle, a music loop | `model/sounds/` | `Sound.Play(Sounds.Blip)` |

## Signature      {#signature}
```osy syntax
Sound.Play(Sounds.Blip)                    // one shot; overlaps with itself and with anything else
Sound.Play(Sounds.Blip, volume: 0.6)       // volume is 0..1; absent means full

Sound.Loop(Sounds.Valley)                  // start looping — background music, an engine hum, rain
Sound.Loop(Sounds.Valley, volume: 0.2)
Sound.Stop(Sounds.Valley)                  // stop that loop
Sound.StopAll()                            // stop every loop
```

Every verb returns nothing. Playback is fire-and-forget from your app's point of view.

## Description    {#description}
The verbs run on the **client**, because that is where the speakers are. You can call them from an ordinary action,
from an `on change` block, or from `on frame` in a game — there is no server round trip and nothing to await.

### The name is a value, not a string   {#names}
`Sounds` is a real type, synthesized from the files you ship. So a sound goes wherever a value goes — into a local,
across a component boundary as a parameter, through a `switch`:

```osy title="a component that takes the sound to make" test app=arcade
[Composable]
component Blipper(Sounds note, string label) {
  action Poke() { Sound.Play(note, volume: 0.5); }
  render { Pressable(label, onClick: Poke); }
}

[Page("/blip")] [AllowAnonymous]
component BlipPage() {
  render {
    Stack(gap: 2) {
      Blipper(note: Sounds.Blip, label: "blip");
      Blipper(note: Sounds.Blip, label: "again, quietly");
    }
  }
}
```

That is why the argument is `Sounds.Blip` and never a bare `blip` or a `"blip.wav"`. Both of those are refused, each
with the rewrite that fixes it:

```text
Sound.Play(blip)        →  `blip` is a declared sound, but a sound is read through its own
                           vocabulary — write `Sounds.Blip`.

Sound.Play("blip.wav")  →  `Sound.Play` takes a declared sound, not a file name — write
                           `Sounds.Blip`. (The app's sounds are compiled in and content-addressed,
                           so there is no path to spell.)
```

### Nothing plays until the visitor has interacted   {#autoplay}
Every browser refuses to start audio until the person has clicked, tapped or typed something on the page, and it
refuses **silently** — no error, no warning, just nothing. This is not something you work around; it is a rule about
consent that every site is subject to.

The platform handles the mechanics: the first gesture the page sees unlocks audio, and everything after that plays
normally. What you have to handle is the **design** consequence — a sound that is meant to play the instant the page
opens will not. Put the first sound behind something the visitor does.

```osy syntax
// ✗ nothing will be heard: the page has had no interaction yet
component TitleScreen() {
  on mount { Sound.Loop(Sounds.Valley); }
}

// ✓ the music starts when they start
component TitleScreen() {
  action Begin() { Sound.Loop(Sounds.Valley); Navigation.Go("/game"); }
  render { Pressable("Play", onClick: Begin); }
}
```

### A loop replaces itself   {#loops}
`Sound.Loop(Sounds.Valley)` while `Valley` is already looping **stops the first one**. That is what you want: an app
that calls it from a render pass or on every state change would otherwise stack a fresh copy of the track on top of
the last one, over and over, with no way back short of reloading the page.

One consequence worth knowing: two independent loops of the *same* file are not expressible. Two loops of *different*
files are ordinary — each name is its own loop.

```osy syntax
Sound.Loop(Sounds.Engine);        // both play
Sound.Loop(Sounds.Wind);
Sound.Stop(Sounds.Engine);        // the wind keeps going
Sound.StopAll();                  // now nothing is looping
```

`Sound.StopAll()` stops loops only. A one-shot already in flight is milliseconds long and is left to finish — when
you reach for StopAll you mean "stop the music", not "cut the click that is half-played".

### Which formats, and why the file name does not decide   {#formats}
`.mp3`, `.wav`, `.ogg` and `.m4a`. The type a sound is served under is read from its **bytes** at compile time, never
from its extension — so a file whose name disagrees with its contents is a compile error naming both, rather than a
file served as something it is not.

Two rejections are worth knowing about because they look like they should work:

- An **`.mp4` holding video** is refused. A browser cannot decode it as audio, so accepting it would turn a
  wrong-file mistake into a sound that never plays and never says why.
- A **`.png` renamed to `.wav`** is refused, and so is any other image. Put a picture in `model/textures/`.

### One sound per name, and where the files live   {#one-per-name}
A sound name resolves to exactly one file. Two files with the same stem — `blip.wav` and `blip.mp3` — is an error
naming both, rather than a result that depends on which one the file system happened to list first.

The default glob is `**/sounds/*` at any depth, so `model/sounds/`, `model/pages/sounds/` and a kit's vendored
`ui/lib/sounds/` are all picked up. To put them somewhere else, declare the role in `app.osy`:

```osy syntax
app Arcade {
  model  "model/**/*.osy";
  sounds "audio/*.wav";        // instead of the default
}
```

### Size, and what is loaded when   {#limits}
A single sound may be up to **16 MB**, and one app's sounds up to 200 MB in total. Both are generous for their
purpose: an effect is a few kilobytes and a music track is a few megabytes.

Sounds under **1 MB are decoded when the page loads**, so the first effect you play is instant — a jump noise that
arrives 300 ms after the jump is worse than no jump noise. Larger files are fetched the first time you play them,
which is right for music: it starts once, and a few hundred milliseconds before it begins is inaudible.

### When nothing is heard   {#silence}
Silence is also what a correct app with no sound produces, so this surface says out loud what went wrong. Open the
browser console: every failure reports once, naming the sound and the reason — a name the app does not ship, a file
that would not fetch, a codec the browser would not decode, or audio the visitor has not unlocked yet.

## See also   {#see-also}
- [textures](https://osysharp.com/reference/ui/textures/) — the raster half of your app's assets, on the same file-is-the-declaration model
- [icons](https://osysharp.com/reference/ui/icons/) — single-colour glyphs
- [Canvas](https://osysharp.com/reference/ui/canvas/) — the drawing surface a game pairs sound with
- [component](https://osysharp.com/reference/ui/component/) — actions, `on frame`, and where these verbs are called from
