{"record":{"id":"3b2854b8daf785a3","repo":"moeru-ai/airi","slug":"pcm16-input-must-contain-complete-16-bit-samples","errorCode":null,"errorMessage":"PCM16 input must contain complete 16-bit samples.","messagePattern":"PCM16 input must contain complete 16-bit samples\\.","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/audio/src/encoding/wav.ts","lineNumber":65,"sourceCode":"  for (let i = 0; i < samples.length; i++) {\n    const sample = Math.max(-1, Math.min(1, samples[i]))\n    const value = sample < 0 ? sample * 0x8000 : sample * 0x7FFF\n    dataView.setInt16(i * Int16Array.BYTES_PER_ELEMENT, value, true)\n  }\n\n  return output\n}\n\n/**\n * Converts little-endian signed PCM16 bytes to normalized Float32 PCM samples.\n *\n * @example\n * toFloat32FromPCM16(new Uint8Array([0, 128, 0, 0, 255, 127]))\n * // => Float32Array([-1, 0, 0.999969482421875])\n */\nexport function toFloat32FromPCM16(pcmBytes: Uint8Array): Float32Array<ArrayBuffer> {\n  if (pcmBytes.byteLength % Int16Array.BYTES_PER_ELEMENT !== 0)\n    throw new TypeError('PCM16 input must contain complete 16-bit samples.')\n\n  const dataView = new DataView(pcmBytes.buffer, pcmBytes.byteOffset, pcmBytes.byteLength)\n  const output = new Float32Array(pcmBytes.byteLength / Int16Array.BYTES_PER_ELEMENT)\n\n  for (let i = 0; i < output.length; i++)\n    output[i] = dataView.getInt16(i * Int16Array.BYTES_PER_ELEMENT, true) / 0x8000\n\n  return output\n}\n\n/**\n * Encodes Float32 samples as a WAV file.\n *\n * @example\n * toWav(float32Samples.buffer, 24000)\n * // => WAV data with converted PCM16 samples\n */\nexport function toWav(buffer: ArrayBufferLike, sampleRate: number, channel = 1): ArrayBuffer {","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/moeru-ai/airi/blob/0616eabd5ba89c7bcef0eb1da84694b9c285c573/packages/audio/src/encoding/wav.ts#L47-L83","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst floats = toFloat32FromPCM16(chunk) // throws on odd chunk\n\n// after\nif (chunk.byteLength % 2 !== 0) {\n  pending = concat(pending ?? [], chunk)\n  chunk = new Uint8Array(0)\n}\nconst floats = toFloat32FromPCM16(chunk)","handlingStrategy":"validation","validationCode":"if (pcmBytes.byteLength % Int16Array.BYTES_PER_ELEMENT !== 0) {\n  // buffer the tail byte and wait for more data, or reject the chunk upstream\n  throw new Error(`Incomplete PCM16 frame: ${pcmBytes.byteLength} bytes`)\n}\nconst floats = toFloat32FromPCM16(pcmBytes)","typeGuard":"function hasCompletePCM16Frames(bytes: Uint8Array): boolean {\n  return bytes.byteLength % Int16Array.BYTES_PER_ELEMENT === 0\n}","tryCatchPattern":"try {\n  features = base64ToFeatures(b64)\n} catch (err) {\n  if (err instanceof TypeError && err.message.includes('complete 16-bit samples')) {\n    // drop/repair the truncated payload rather than crashing the stream\n  } else throw err\n}","preventionTips":["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."],"tags":["audio","pcm16","validation","byte-alignment"],"backgroundTag":"buffer-length-validation","analyzedSha":"0616eabd5ba89c7bcef0eb1da84694b9c285c573","analyzedAt":"2026-08-28T13:11:01.995Z","contentChangedAt":"2026-08-28T13:11:01.995Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}