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
Every relative path resolves against one root: the directory -config names.
It does not matter which document the path is written in.
kind: Extension
metadata: { name: "200", tenant: acme }
spec:
steps:
- type: play
file: ../sounds/welcome.wav # configs/ → sounds/ at the repository root
---
kind: System
spec:
moh:
default: ../sounds/moh/default.wav # the same prefix, from another kind
See Documents 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:
kind: System
spec:
moh:
default: ../sounds/moh/default.wav
classes:
jazz: ../sounds/moh/jazz.wav
corporate: ../sounds/moh/corporate.wav
---
kind: Tenant
metadata: { name: acme }
spec:
domains: [acme.pbx.example.com]
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.
A plan that runs off its last step hangs the caller up, so an announcement at the
bottom of a file does not need a hangup after it. Writing one anyway makes
the intent obvious, and it is required when the announcement is not the last
step:
steps:
- id: closed
type: play
file: ../sounds/closed.wav
- id: done
type: hangup
Generating prompts
The fragments voicemail and the menus assemble are generated from a manifest
rather than recorded by hand, and -tts can produce a one-off announcement in
the same voice:
./bin/lyno -tts -text "We are closed for the holidays" -out sounds/closed.wav
Synthesis happens at configuration time and never during a call. See Prompt audio.