Channel Formats
libsonare recognizes four channel layouts system-wide — mono, stereo, 5.1 and 7.1 — but different modules were built for different jobs, so a channel count above 2 is not handled the same way everywhere. This page is the map: which module accepts how many channels, what canonical order and LFE handling mean, and how narrowing and widening work. There is one downmix rule and two upmix behaviors.
Layouts and Speaker Roles
| Layout | Channels | Roles, canonical order |
|---|---|---|
| Mono | 1 | (no role; a single channel) |
| Stereo | 2 | L, R |
| 5.1 | 6 | L, R, C, LFE, Ls, Rs |
| 7.1 | 8 | L, R, C, LFE, Ls, Rs, Lss, Rss |
Canonical order matches WAVE_FORMAT_EXTENSIBLE. In 7.1, Ls/Rs (indices 4/5) and Lss/Rss (indices 6/7) are two distinct role pairs, not the same pair renamed — see Widening for where each one sits.
The figure below places each role around the listener, with its plane index. Loudness metering weighs a channel by +1.5 dB when its speaker sits between 60° and 120° to either side, so the 5.1 surround pair and the 7.1 side pair are weighted while the 7.1 rear pair is not.
- L / R front pair
- C centre
- Ls / Rs surround (rear in 7.1)
- Lss / Rss side
- loudness weight +1.5 dB (60°–120°)
- LFE: no direction, not in the loudness sum
LFE (low-frequency effects) carries only the bass content mixed into the track at reduced level, on the assumption a decoder boosts it back by about 10 dB on the way to a subwoofer. In the playback renderer, three rules cover every case a target can end up in. The shared downmix function used elsewhere simply drops the LFE unless includeLfe is set (see Narrowing).
- Fold-in (no LFE channel on the target, or bass management has no subwoofer): the LFE signal is low-pass filtered at 120 Hz and added into the L/R pair at reduced level.
- Subwoofer feed (bass management enabled with a subwoofer): the LFE signal is boosted and combined with the low-frequency content crossed over from speakers marked
"small", then sent to the LFE channel. - With bass management disabled and a target that does have an LFE channel, the LFE channel passes through unchanged, because the downstream amplifier is assumed to apply its own +10 dB.
Channel Handling by Module
Most of the library processes mono or stereo and rejects anything wider; only the modules below accept a surround channel count, and each one decides differently what to do with material wider than what it renders.
| Module | Accepts | Wider-than-stereo behavior |
|---|---|---|
| Audio file loading | Folds to mono, or passes an interleaved buffer through untouched | 1/2/6/8-channel files fold to mono through the BS.775 downmix below; any other count is averaged |
| Analysis and streaming analysis | Mono only | Caller folds first |
Loudness metering (lufsInterleaved) | Any channel count | BS.1770 channel weights are selected by channel count |
| True peak, correlation, phase scope | Mono / stereo | Rejected |
| One-shot effects and mastering processors | 1–2 | Rejected |
Repair session (masteringRepairAnalyze / masteringRepairApply) | Any non-empty planar channel set | Runs the selected stages across all channels; denoise and dereverb use linked masks |
| Realtime processors (dynamics, EQ, …) | Up to 64 planar | Runs per-plane; a stereo-pair-only effect on a wide bus acts on the front pair only |
| Mixing bus, strip, meter | 1 / 2 / 6 / 8 | Follows the bus's declared layout |
| Mixer graph | Bus width as declared | A narrower destination (a smaller bus, a stereo sidechain key) gets the BS.775 downmix; a wider one receives the source planes unchanged |
| Realtime engine / project bounce | Bus layout; output is 1 / 2 / 6 / 8 | Same rule as the mixer graph; a request wider than the master bus is refused. In an AudioWorklet, the meter snapshot of a 5.1 or 7.1 target adds per-channel arrays |
Realtime engine track lane (setTrackLanes) | Stereo source only | Mono, 5.1 and 7.1 sources are refused (InvalidParameter; RangeError in the WASM worklet facade). A C caller sets SONARE_CHANNEL_LAYOUT_STEREO, because a zero-initialized lane reads as mono. setTrackLanes also refuses more than MAX_TRACK_LANES (32) lanes, a lane pan outside −1…+1, a width outside 0…2 and a fader outside the fader range |
| Realtime voice changer | 1–2 | Rejected |
| Playback renderer | 1 / 2 / 6 / 8, plus a channel map | Follows the conversion table below in both directions |
Declaring an Input Layout with a Channel Map
Decoders disagree on channel order. AAC uses C L R Ls Rs LFE, some frameworks distinguish "5.1" from "5.1(side)", and platform APIs each have their own convention. The playback renderer's input.channel_map names the role of each input channel explicitly (input.layout must then be a fixed layout, not "auto") instead of asking every caller to reorder its own buffers first: an incorrect map fails loudly as a validation error rather than landing as a silently swapped or missing channel. A 5.1(side) source's SL/SR channels map onto the Ls/Rs roles; the role name is "left/right surround"; the angle it ends up at is decided by the output side, not the input labeling.
Narrowing (Downmix)
Every module that renders a surround bus to something narrower (file loading, the mixer graph, project bounce and the playback renderer) uses the same ITU-R BS.775 coefficients, exposed as downmix in the mixing module. BS.775 itself defines the downmix for formats up to 5.1; the 7.1 rules below extend it by folding each side pair into its surround pair. The surround pair enters the front pair at reduced level, and the LFE channel stays out of the arithmetic: downmix drops it unless includeLfe is set, while the playback renderer applies its own LFE fold-in (above). In JavaScript the same coefficients are downmix(channels, targetLayout): centre and surround enter the front pair at -3 dB and the LFE plane is dropped. decodeChannels(bytes) keeps every channel of a file so you can narrow it yourself, and the browser-codec fallback of Audio.fromMemoryWithBrowserFallback folds to mono through this rule, so both decoders give the same samples.
downmix reads the source layout from the channel count (1, 2, 6 or 8). The target is a ChannelLayout ordinal: 0 mono, 1 stereo, 2 5.1, 3 7.1.
| Source | Target | Rule |
|---|---|---|
| Stereo | Mono | Plain average of L and R |
| 5.1 or 7.1 | Mono | The stereo fold below, then the average of the two channels |
| 5.1 | Stereo | L + 0.707 C + 0.707 Ls, and the same for the right side; LFE dropped |
| 7.1 | Stereo | As 5.1, with Lss added at -3 dB beside Ls |
| 7.1 | 5.1 | Ls + Lss and Rs + Rss summed at unity gain; the other channels pass through |
| Same layout | Same layout | Copied |
| Any | A wider layout | Refused: InvalidParameter, "downmix cannot upmix" |
| Any other channel count | Mono | Unweighted mean of the channels |
| Any other channel count | Any other target | Refused: InvalidParameter |
The fold is not normalized by default
The fold is not scaled to stay inside full scale. A full-scale, in-phase 5.1 source reaches 2.41 in stereo and in mono, and a 7.1 source reaches 3.12 in stereo. Audio.fromMemory of a 5.1 file uses the same fold, so it can also exceed full scale. Pass normalize (below) or limit the result before writing it.
Two options change the fold. normalize divides the output by the worst-case sum, so a source with every plane at full scale and in phase lands at exactly 1.0; the divisor is fixed by the layouts, not measured from the signal, so typical material comes out a few dB quieter. includeLfe folds the LFE plane into the front pair at -3 dB instead of dropping it, which raises the same worst case to 3.12 (5.1) or 3.83 (7.1). The options apply to a narrowing into stereo or mono; a 7.1 to 5.1 fold keeps the LFE plane either way. They are { normalize, includeLfe } in JavaScript and normalize=, include_lfe= in Python; the C form is sonare_downmix_ex.
import { init, decodeChannels, downmix } from '@libraz/libsonare';
await init();
const { channels, sampleRate } = decodeChannels(fileBytes);
const stereo = channels.length > 2 ? downmix(channels, 1) : channels;from libsonare import decode_channels, downmix, ChannelLayout
channels, sample_rate = decode_channels(file_bytes) # (channel_count, frames), float32
stereo = downmix(channels, ChannelLayout.STEREO) if channels.shape[0] > 2 else channelsSee JavaScript/TypeScript Audio and Streaming API for the signatures, C++ API for the C and C++ forms, and Surround group buses and wide meters for the mixer graph's own narrowing rule.
The playback renderer's own conversion table, by source and target. "Rendered binaurally" means the speaker feeds are filtered through measured head-related responses (HRTF), so the speakers seem to sit outside the head:
| Source \ Target | Stereo speakers | 5.1 speakers | 7.1 speakers | Headphones |
|---|---|---|---|---|
| Mono | L/R, -3 dB | Centre only | Centre only | Centre, rendered binaurally |
| Stereo | Passthrough | Upmix (or L/R only, upmix.enabled: false) | Upmix (or L/R only) | Upmix, then rendered binaurally (or L/R only at ±30°) |
| 5.1 | BS.775 downmix + LFE fold-in | Passthrough | L/R/C/LFE unchanged; Ls/Rs move to Lss/Rss; 7.1's own Ls/Rs are silent | 5.1, rendered binaurally |
| 7.1 | BS.775 downmix + LFE fold-in | BS.775 downmix (LFE unchanged) | Passthrough | 7.1, rendered binaurally |
Widening (Upmix)
The mixer and the playback renderer widen channel beds differently. The mixer sums a narrower bus into the same plane indices of a wider bus, leaving the additional planes silent. The playback renderer uses speaker geometry when mapping ambience to a wider layout.
| Conversion | Mixer | Playback renderer |
|---|---|---|
| 5.1 to 7.1 | Ls/Rs stay at indices 4 and 5; the side planes at 6 and 7 stay silent | Ls/Rs move to the side planes at 6 and 7; the rear planes at 4 and 5 stay silent |
| Stereo to 7.1 | Front pair only; other planes silent | Ambience generated for both rear and side pairs, with an extra anti-phase component on the rear pair |
The surround panner uses the speaker angles in the layout figure in both modules, but they only place a panned source; they do not decide how a bus is widened. At full pan the panner silences the far channel exactly.
The renderer does not create a rear pair from a 5.1 source (no synthetic rear-channel generation), and it does not upmix to a wide-plus-height format — see Playback Renderer § Non-Goals.