Audio files
Everything Lyno plays — greetings, announcements, music on hold — must be:
8 kHz, mono, 16-bit PCM WAV
That is not a preference. It is the format that can be encoded to G.711 alaw/ulaw on the wire without resampling, and anything else is rejected.
Validation is at startup
Files are parsed, validated and cached in memory when the PBX starts, so a bad file fails fast instead of mid-call. The checks and their messages:
| Problem | Message |
|---|---|
| Not a WAV container | wav: not a RIFF/WAVE file |
| Compressed audio | wav: audio format N is not PCM |
| Stereo | wav: N channels, expected mono |
| Wrong sample rate | wav: sample rate N, expected 8000 |
| Wrong bit depth | wav: N bits per sample, expected 16 |
Caching in memory also means the files are read once: replacing a WAV on disk has no effect until the PBX restarts.
Converting
ffmpeg -i input.mp3 -ar 8000 -ac 1 -acodec pcm_s16le output.wav
| Flag | Why |
|---|---|
-ar 8000 | 8 kHz sample rate |
-ac 1 | mono |
-acodec pcm_s16le | 16-bit signed little-endian PCM |
Check a file you did not produce yourself:
ffprobe -v error -show_entries stream=codec_name,sample_rate,channels,bits_per_raw_sample output.wav
Two practical notes on the source material: normalise before downsampling, since 8 kHz mono leaves no headroom to fix a quiet recording, and low-pass anything with music in it — content above 4 kHz cannot survive the sample rate and only adds aliasing.
Where files live
Paths inside a tenant's files resolve against the tenant directory:
- type: play
file: ../../../sounds/welcome.wav # → sounds/ at the repository root
Paths in lyno.yaml resolve against the directory holding it:
moh:
default: ../sounds/moh/default.wav
See Files and paths for the full rule.
Music on hold classes
Music on hold is referenced by class name, never by path, so the file behind a class can differ per tenant:
moh:
default: ../sounds/moh/default.wav
classes:
jazz: ../sounds/moh/jazz.wav
corporate: ../sounds/moh/corporate.wav
moh:
default: ../../sounds/moh/acme.wav # acme's own default
classes:
jazz: ../../sounds/moh/acme-jazz.wav # overrides the global jazz
Resolution for a class name:
- the tenant's
moh.classes; - the global
moh.classes; - for the special name
default, the tenant'smoh.defaultthen the globalmoh.default.
A step naming a class no level provides is a validation error, as is using
default when neither level defines one.
Using a class:
- id: hold
type: moh
moh: jazz
- id: ring
type: dial
moh: default # caller hears music instead of ringback
target: {kind: ring_group, group: support}
Omitting moh on a dial step leaves the caller with normal ringback.
Length and looping
Music-on-hold classes loop until the call moves on. Announcements play once and
then the plan continues, so an announcement that should be the last thing a
caller hears needs an explicit hangup after it:
- id: closed
type: play
file: ../../../sounds/closed.wav
- id: done
type: hangup