μ-law is a companding algorithm used in telephony (North America, Japan). It compresses 14-bit linear PCM into 8-bit samples, giving better quality for quiet sounds at the expense of loud sounds.
Mulaw Byte Structure:
Bit: 7 6 5 4 3 2 1 0
+----+----+----+----+----+----+----+----+
| S | Exponent | Mantissa |
+----+----+----+----+----+----+----+----+
S = Sign bit (0 = positive, 1 = negative)
Exponent = 3 bits (0-7)
Mantissa = 4 bits (0-15)
Decoding Formula:
sample = ((mantissa << 3) + bias) << exponent
sample = sample - bias
if (sign_bit) sample = -sample
The Bias Value Problem:
The bias value is critical for correct decoding:
| Bias Value | Hex | What Happens |
|---|---|---|
| 33 (wrong) | 0x21 | Samples decoded ~4x too quiet, then shifted incorrectly |
| 132 (correct) | 0x84 | Proper dynamic range, correct audio |
Why 132 (0x84)?
The μ-law standard uses bias = 132 because:
- It provides the optimal quantization step sizes
- Matches the encoder's bias value
- Results in proper 14-bit PCM reconstruction
Visual: Wrong vs Correct Decoding
Original audio wave: ~~~~∿∿∿~~~~
With bias=33 (wrong): _-_-_-_-_-_ (clipped, distorted)
With bias=132 (correct): ~~~~∿∿∿~~~~ (matches original)
The Masking Problem:
JavaScript's bitwise NOT (~) operates on 32-bit integers:
// Without masking
~0xAB = 0xFFFFFF54 // 32-bit result, wrong!
// With masking
(~0xAB) & 0xFF = 0x54 // 8-bit result, correct!Complete Correct Implementation:
function mulawToPcm(mulawData) {
const pcmData = new Float32Array(mulawData.length);
for (let i = 0; i < mulawData.length; i++) {
// Step 1: Invert bits and mask to 8-bit
let mulaw = (~mulawData[i]) & 0xFF;
// Step 2: Extract sign (bit 7)
let sign = (mulaw & 0x80) ? -1 : 1;
// Step 3: Extract exponent (bits 4-6)
let exponent = (mulaw >> 4) & 0x07;
// Step 4: Extract mantissa (bits 0-3)
let mantissa = mulaw & 0x0F;
// Step 5: Decode with correct bias (0x84 = 132)
let sample = ((mantissa << 3) + 0x84) << exponent;
sample -= 0x84;
// Step 6: Apply sign and normalize to [-1, 1]
pcmData[i] = (sign * sample) / 32768.0;
}
return pcmData;
}