moeru-ai/airi · error · TypeError
PCM16 input must contain complete 16-bit samples.
Error message
PCM16 input must contain complete 16-bit samples.
What it means
Thrown by toFloat32FromPCM16 when the byte length of the PCM byte array is not an even multiple of 2 (Int16Array.BYTES_PER_ELEMENT). Every 16-bit PCM sample occupies exactly two little-endian bytes, so an odd-length buffer cannot represent complete samples and conversion is refused rather than silently dropping or misreading a byte.
Source
Thrown at packages/audio/src/encoding/wav.ts:65
for (let i = 0; i < samples.length; i++) {
const sample = Math.max(-1, Math.min(1, samples[i]))
const value = sample < 0 ? sample * 0x8000 : sample * 0x7FFF
dataView.setInt16(i * Int16Array.BYTES_PER_ELEMENT, value, true)
}
return output
}
/**
* Converts little-endian signed PCM16 bytes to normalized Float32 PCM samples.
*
* @example
* toFloat32FromPCM16(new Uint8Array([0, 128, 0, 0, 255, 127]))
* // => Float32Array([-1, 0, 0.999969482421875])
*/
export function toFloat32FromPCM16(pcmBytes: Uint8Array): Float32Array<ArrayBuffer> {
if (pcmBytes.byteLength % Int16Array.BYTES_PER_ELEMENT !== 0)
throw new TypeError('PCM16 input must contain complete 16-bit samples.')
const dataView = new DataView(pcmBytes.buffer, pcmBytes.byteOffset, pcmBytes.byteLength)
const output = new Float32Array(pcmBytes.byteLength / Int16Array.BYTES_PER_ELEMENT)
for (let i = 0; i < output.length; i++)
output[i] = dataView.getInt16(i * Int16Array.BYTES_PER_ELEMENT, true) / 0x8000
return output
}
/**
* Encodes Float32 samples as a WAV file.
*
* @example
* toWav(float32Samples.buffer, 24000)
* // => WAV data with converted PCM16 samples
*/
export function toWav(buffer: ArrayBufferLike, sampleRate: number, channel = 1): ArrayBuffer {View on GitHub (pinned to 0616eabd5b)
Solutions
- Verify pcmBytes.byteLength % 2 === 0 before calling; if odd, buffer the trailing byte and prepend it to the next chunk.
- If chunking, accumulate bytes and only convert on even boundaries (carry over the leftover byte).
- Check the upstream producer/transport (base64 decode, WebSocket frame, file slice) for truncation or off-by-one slicing.
- Confirm the source audio is actually 16-bit PCM, not 8-bit or 24-bit.
Example fix
// before
const floats = toFloat32FromPCM16(chunk) // throws on odd chunk
// after
if (chunk.byteLength % 2 !== 0) {
pending = concat(pending ?? [], chunk)
chunk = new Uint8Array(0)
}
const floats = toFloat32FromPCM16(chunk) Defensive patterns
Strategy: validation
Validate before calling
if (pcmBytes.byteLength % Int16Array.BYTES_PER_ELEMENT !== 0) {
// buffer the tail byte and wait for more data, or reject the chunk upstream
throw new Error(`Incomplete PCM16 frame: ${pcmBytes.byteLength} bytes`)
}
const floats = toFloat32FromPCM16(pcmBytes) Type guard
function hasCompletePCM16Frames(bytes: Uint8Array): boolean {
return bytes.byteLength % Int16Array.BYTES_PER_ELEMENT === 0
} Try / catch
try {
features = base64ToFeatures(b64)
} catch (err) {
if (err instanceof TypeError && err.message.includes('complete 16-bit samples')) {
// drop/repair the truncated payload rather than crashing the stream
} else throw err
} Prevention
- Accumulate stream chunks in a queue and drain only even byte counts, carrying over leftover bytes.
- Validate payload length at the transport boundary (base64 length, WebSocket frame size) before decoding.
- Write a unit test with odd-length inputs to lock in the buffering behavior.
When it happens
Trigger: Calling toFloat32FromPCM16 (directly, or via pumpPcm16Input / base64ToFeatures) with a Uint8Array whose byteLength % 2 !== 0. Common with chunked stream reads that split a frame across chunks, or a base64 payload that was truncated by one byte.
Common situations: Audio capture callbacks delivering arbitrary byte counts; WebSocket/base64 transfer truncating payloads; slicing a buffer with a wrong offset/length; feeding 8-bit or 24-bit PCM into a PCM16 decoder; reinterpreting a subarray with a misaligned byteOffset.
Related errors
- No audio file provided for transcription.
- MiMo voice clone requires a base64 audio sample in data URI
- MiMo voice design requires a style prompt in the user messag
- MiMo transcription failed: ${response.status} ${response.sta
- Skipping malformed SSE chunk from OpenRouter audio stream:
AI-assisted analysis of moeru-ai/airi@0616eabd5b (2026-08-28).
Data as JSON: /api/errors/3b2854b8daf785a3.
Report an issue: GitHub.