# D67. Sounds: interface sounds, sounds of the story, music and ambiences on one engine, CC0 in the core (28 Oct 2026)

The founder asked for sounds between the lines of a dialogue (the phone that rings in phishing-by-numbers), for interface sounds in dialogues (a choice, a good, fair or bad feedback, going back), default sounds that authors can switch off or replace, and later music. Research came first (28 Oct 2026: what learning research and accessibility say, Storyline and Rise, every free source and its licence, a default library, ElevenLabs' prices). The founder accepted it with four changes: music and ambiences come now, being one thing technically (a long track, looped, lowered under the voice, that learners can switch off); the core's library is CC0 only; captions of sounds are advised, never imposed; the default interface sounds are named by him. This entry decides what the research left open.

## Three layers

| | Interface sounds | Sounds of the story | Music and ambiences |
|---|---|---|---|
| What | A choice, a feedback good, fair or bad, going back or replaying, opening and closing a history or a debrief, a dialogue's successful end; a question's feedback | The phone that rings, a door, a notification: a sound that says something of the scene | An opening, a transition, a loop under a scene; an ambience of one screen |
| What starts it | Always a learner's gesture | The walk of a dialogue, a narration's timeline, an event | A screen within the range of the track, or an event (`play-sound` with `loop`) |
| Learner | On when the module has sound (below); « Sons de l'interface » in « Affichage » | Part of the content: the player's volume and mute, as the voice | « Musique et ambiances » in « Affichage » |
| Level | The course's `sounds.interface.volume` (0.5 by default) under the player's volume | The author's mix | Lowered by 20 dB at least while a voice speaks (WCAG 1.4.7) |

**When interface sounds are on.** `sounds.interface.when` is `auto` by default: on when the module has sound (a voice line whose audio is there, in a narration, a layer or a dialogue; an audio or video block; a sound of the story; music or an ambience), off otherwise, so that a silent module followed in an open space does not start clicking; `on` and `off` force it. The research found no study of click and feedback sounds in e-learning: they are for the legibility of the interface and for pleasure, not for learning, hence a default that follows what the module already does.

**The same sounds everywhere.** A question's feedback makes the sound of a dialogue's feedback (good or bad), so that a module speaks one language. The default set is the founder's choice from Kenney's Interface Sounds (CC0): `select_001` (a choice), `confirmation_001` (good), `question_001` (fair), `error_001` (bad), `scratch_004` (going back, starting over, replaying), `maximize_006` and `minimize_006` (opening and closing the history or the debrief). The successful end's jingle is Freesound 521640, « WinBanjo » by Fupicat (CC0): its original needs a Freesound account, so the founder downloaded it (28 October) and `pnpm run sounds` makes the role from the library's master `fin-reussie`. An author mutes a role (`replace: { "bad": null }`) or gives it a sound of the course (`replace: { "bad": "buzzer" }`).

## One engine in the player

- **One `AudioContext` for the page** (`@elabora/player` `sounds.ts`), made at the learner's first pointer or key press, never at load: browsers keep a context made before a gesture suspended, and an LMS frame of another origin gets the gesture in its own document. Interface sounds always follow a gesture, so they sound at once; what asked to sound before the first gesture (an ambience at the start of the first screen) starts with it. The narration and the dialogues' lines keep their own `<audio>`, the clock of their timelines (D24, D61).
- **The player's volume and mute** (the narration's controls) set the context's master gain: they apply to every sound.
- **Interface sounds** are decoded once, played from memory, at once or not at all (a click heard a quarter of a second late is worse than none).
- **Files.** The player's own interface sounds ship as `elabora/sounds/<role>.m4a` (AAC, mono, 96 kb/s, 45 KB for the eight), only in packages whose course data names them: a course without sound keeps its bytes. They are made once from the originals by `pnpm run sounds` (`packages/player/src/interface-sounds/sources.json`, kept by hand, with each original's SHA-256). Measured on 28 Oct 2026 in Chrome 152: `decodeAudioData` reads M4A, MP3, FLAC, Ogg Vorbis and Opus with no added silence (the encoders' priming is trimmed), so M4A, the packages' audio format since D16, serves here too.

## Sounds of the course

- **Files.** A sound is `assets/sounds/<id>.<ext>`, named by its id wherever it is used, with its sidecar `<id>.json` (schema `sound`): its title, where it comes from (`source`, `url`, `sourceId`), its `author`, its `license`, the `credit` line a licence asks for, what was done to it (`modified`, `trim`), its `duration`, whether it was made to `loop`, a suggested `caption` by language, and `generated` for a sound an AI service made.
- **Storage.** A sound is kept as it came when it came lossy (Vorbis, Opus, AAC, MP3: never transcoded from one lossy format to another before the build), and as FLAC when it came lossless (`elabora sound add` stores a WAV as FLAC), as AAC at 256 kb/s from 20 s or made to loop (below). Those masters are conformant under `assets/sounds/` (no `W_MEDIA_NOT_OPTIMIZED`). The build makes the package's M4A from them at every media quality, `high` included, since browsers and LMS players need AAC or MP3. The packages' audio format is unchanged (D16).
- **Three ways to sound.** A **sound line** in a narration (the screen's or a layer's) or a dialogue: `{ id, sound, caption?, volume? }` (and `if` in a dialogue), at its place among the lines, as long as its sound. In a dialogue it is a beat of the walk: its caption shows in the thread as a narration, the next line waits for its end, and the dialogue's script, paths and translation show it. A narration's sound line is laid into the narration's track by the build, so that pausing, seeking and the speeds hold for it as for the voice; it is no voice's (the voice tools, alignment and transcription never see it). An **action** `play-sound` (`sound`, `volume`, `loop`, `fade`, `caption`) and `stop-sound` (`sound`, or every sound of the story; `fade`), in events, cues and a dialogue's options and lines (a presentation action, D61: a replayed path plays none). A **background track** of the course (`sounds.background`): `{ sound, from, to?, volume?, fade? }` over a range of screens.
- **A sound of the story stops at the end of its screen**, and so does an ambience a screen starts (`play-sound` with `loop`). A background track plays on while the next screen is in its range, without starting over, and fades out when the learner leaves it. Ranges do not overlap (`E_SOUND_BACKGROUND`).

## Music and ambiences: one mechanism

- **A loop with a crossfade.** Copies of the decoded sound are scheduled on the context's clock, each fading in over the crossfade (the track's `fade`, 2 s by default, a third of the sound at most) as the one before fades out. A crossfade also hides the silence some encoders leave at a file's start. ElevenLabs' music has no loop of its own: this makes one.
- **Lowered under the voice.** While a media element of the page plays (the narration's, a layer's, an audio or video block, a dialogue's line), music and ambiences go 20 dB down (a tenth of the amplitude, WCAG 1.4.7), in a quarter of a second, and come back in 0.8 s.
- **Learners switch them off** in « Affichage » (« Musique et ambiances », kept for the course with the display choices). A course with music or ambiences and « Affichage » disabled is `W_SOUND_MUSIC_SWITCH_HIDDEN` (WCAG 1.4.2: audio that plays by itself for more than 3 seconds can be stopped; the player's volume shows only on narrated screens).
- **Levels**: a background track at 0.25 and an ambience at 0.3 of the player's volume by default.

## Captions: advised, never imposed

A sound's `caption` (« Le téléphone sonne. ») goes with the captions of its narration (in italics) and in the transcript; an action's is said to screen readers and shown over the bottom of the screen while it lasts, when the learner reads captions or when the course gives no way to choose them. Captions are a unit of translation. **No finding asks for one**: the founder's rule is that agents write a caption when the sound carries meaning no text gives, and not when the prose already says it (frequent in dialogues) or when the sound is decorative and serves the immersion. The agents' skills say so; a warning would push captions everywhere.

## Licences and credits

- **What may ship.** CC0, a public-domain mark, CC BY and CC BY-SA, `own` (made or bought by the author with every right) and `generated` (an AI service's output under a plan that gives commercial rights). A sound without a readable sidecar, under an unknown licence, a non-commercial one (NC: a course is a work its author may sell) or a no-derivatives one (ND: a sound cut, faded or mixed is a derivative) is `E_SOUND_LICENSE`. BY-SA is accepted with its credit: its share-alike obligation bears on adaptations of the sound, which the author does not publish apart; search never offers it.
- **« IA et crédits »** gets the credit line of each CC BY or BY-SA sound the course plays (the sidecar's, else « “Title”, Author — CC BY 4.0, modifié — url »), and « Sons générés par IA » for a sound an AI made. A CC0 sound needs none: its provenance stays in its sidecar. A CC BY sound whose credit the player hides is `W_SOUND_CREDITS_HIDDEN`.
- **`CREDITS.txt`** at the root of a package with sounds lists every sound it ships, its source, author, licence and changes, for the LMS's administrators (the player's interface sounds included).

## Storage, measured

The founder asked for a quality that bears a recompression at publication and a weight reasonable for the core and the app. Measured on 28 October 2026 with ffmpeg 9.0.2 on the library's ambiences (30 to 50 s, stereo): each candidate master, then the package's AAC at 160 kb/s made from it, its signal-to-distortion ratio against the lossless original (`asdr`). For the café ambience (45 s): FLAC 3.55 MB, its package file 24.0 dB; AAC 256 kb/s 1.44 MB, 22.3 dB; AAC 192 kb/s 1.08 MB, 21.0 dB; Opus 192 kb/s 0.98 MB, 20.4 dB; Opus 160 kb/s 0.81 MB, 19.4 dB. The rain and the birds ranked them the same way. A short effect barely weighs as FLAC (a keyboard of 4 s: 229 KB), and its encodings all keep the same ratio.

Decided: **short sounds as FLAC, ambiences and music (from 20 s, or made to loop) as AAC at 256 kb/s**, a lossy original kept as it came. The library weighed 9.2 MB this way on 28 October (3.2 MB of effects, 6.0 MB of ambiences), against 19.7 MB all in FLAC, and 11.2 MB on 29 October with the Freesound sounds (3.6 MB of effects, 7.6 MB of six ambiences); its packages' files weigh a fifth of that. Opus would be lighter, but its package file loses more, and Safari's support of Opus came late.

## The library, « Sons courants »

- **In the core, CC0 or public domain only**, in `packages/node/sounds/` (the files, `sources.json` kept by hand with each source's page, author, licence, the date it was read and what was done; `catalogue.json` and `README.md` written by `pnpm run sounds`). Taken once by hand from Kenney (CC0), OpenGameArt (CC0) and Wikimedia Commons (CC0 or public-domain dedications), never by an API in bulk; each analysed (length, silence at both ends, level, clipping, hum under the ambiences), trimmed, levelled (effects to a peak of −3 dBFS, ambiences to an RMS of −26 dBFS) and, for ambiences, given a crossfaded seam. 38 sounds: phones, doors and footsteps, office, notifications, impacts, transitions, nature, crowds and places, stingers, five ambiences. The app's library may add CC BY sounds with their credits.
- **Freesound's originals need an account**: none was taken. The sounds the founder kept and those worth having are listed for him with the cut wanted; meanwhile a CC0 sound stands in, or the hole stays. The office phone's ring was synthesised by Elabora (two tones alternating twenty times a second, in double rings): no CC0 landline ring could be had otherwise.
- **The founder's exclusions hold**: Freesound 459140, 865364, 685984, 529987, 579884, 108278 and 683420, alarms and sirens; sounds with few ratings are distrusted.

## For agents

`elabora sound search` (the library, offline; then `--source openverse`: Freesound and Commons through Openverse without an account, CC0, public domain and CC BY only, since its « commercial » filter lets ND and SA through), `sound add` (a library id, `openverse:<id>` or a file with its licence; `--trim`, `--fade`, `--loop`, `--caption`; one undoable change, `add_sound`), `sound list`, `sound generate` (ElevenLabs' `eleven_text_to_sound_v2` with the author's key: 10 credits a second, as billed in the trial of 28 October and kept in the ledger (D36), where ElevenLabs' documentation said 40; about 0.1 % of a Starter plan for 3 s; D30's threshold, the request never paid twice, a key without the `sound_generation` permission said so). MCP: `elabora_sound_search`, `elabora_sound_list`, `elabora_sound_add`, `elabora_sound_generate`. The skill `elabora-sounds` says when a sound carries meaning, when to caption it, how to place it. A generation's cost goes to the ledger (D36) as `sound-generation` once measured.

## In the desktop app (29 Oct 2026)

The app's sound browser (Lin 46) searches « Sons courants » as one library: the core's (CC0) and the app's own (CC BY sounds the founder took from Freesound, with their credits), which the engine reads through `ELABORA_SOUND_LIBRARIES`. A row says « CC BY » when a sound asks for a credit; the credit is written by Elabora, from the sound's sidecar. Openverse searches only once the author has typed words (its limit is per computer). A sound is heard before it is added (`soundPreview`), cut there, and added with its line in two changes the author undoes one after the other. ElevenLabs follows D30: nothing shown under 1 % of the quota, a confirmation from 1 %. A sound line's caption is suggested in its inspector (the sidecar's), never written by itself.

## Set by ear (schemas 0.1.7, 29 Oct 2026)

The founder sets a sound cue's volume by ear in the app and asked for four more controls: a volume above 100 %, a fade out, a part of the sound per cue, and what plays under the sound lowered. All four are on `play-sound`, in events, cues and a dialogue's options; the mixing rules are one pure module of the player (`sound-mix.ts`), which the app's editor uses too, so that what the author hears is what learners hear.

- **A volume up to 200 % (+6 dB), with no clipping.** A sound recorded too quietly is raised, no further than brings its loudest sample, over the part played, to −1 dBFS (the true-peak ceiling of EBU R128, a margin for the AAC encoding of packages): the player reads the peak from the decoded file. A sound already that loud plays at its own level. 200 % because a library sound that needs more should be normalized once (`modified: normalized`), not boosted at every play; a compressor or limiter was not chosen, since it changes the sound's character where the author only asked for it louder.
- **A fade out** (`fadeOut`, up to 10 s), at the end of the part played; with the fade in, shortened together when both are longer than the part. An ambience's `fadeOut` is how it fades when it stops without a fade of its own (stop-sound's, the screen's end), 0.3 s otherwise.
- **A part of the file** (`start`, `end`, seconds): a cue plays a piece of a longer sound without a second file; an ambience and a background track loop their part. `E_SOUND_PART` when the part is not inside the file or ends before it starts. Cutting a sound when it is added (`trim`) still writes a shorter file: a part is for one use among several.
- **The music and the ambiences lowered under a sound** (`duck`, dB, above 0 and at most 30; the app offers 6, 12 and 20): they go down while a sound played once plays, in 0.1 s as it starts (quick, so its attack is heard over them) and back up in 0.5 s after its end (slower, so they do not jump back), linear in dB, on top of the 20 dB a voice lowers them by. Two sounds at once do not add up: the deepest wins. An ambience never ducks (`W_SOUND_DUCK_LOOP`): it is one of what a duck lowers, and it plays for the whole screen.
- **A sound never lowers a voice** (the founder, 29 Oct; schemas 0.1.8, 0.1.7 staying as it was published): the voice comes first, music and ambiences already go down under every voice, and a sound that must cover a voice is rare; the narration, a dialogue's lines, a layer's voice and audio and video blocks play as they are, the same on every device (a page cannot set a media element's volume on iPhone and iPad, where lowering the voice would have needed it routed through Web Audio, with its own risks).
- **Not now:** sound lines keep `volume` up to 1 (a narration's are laid into its track at the build).

*Not chosen:* a file per click in an `<audio>` element (a delay at each play, and iOS ignores an element's volume, which ducking needs); Web Audio for the voice (the timelines' clocks are the media elements, and they work).

*Revisit if:* an LMS's frame refuses Web Audio; learners ask for a volume of their own for interface sounds or music; music must follow a narration's timeline (a bed that pauses and seeks with it); the UPF lets an app use Freesound's API (then Freesound directly, its originals); ElevenLabs says its music may ship in a course sold to a client (then « Créer une musique »); or Openverse charges for commercial use.
