{"record":{"id":"5b0cdf975061e115","repo":"Hmbown/CodeWhale","slug":"invalid-pet-voice-audio","errorCode":null,"errorMessage":"Invalid pet voice.","messagePattern":"Invalid pet voice\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/src/core/pet-audio.ts","lineNumber":79,"sourceCode":"  }\n}\n\n/** Sample-addressed noise: no global RNG, identical samples when chunked/seeking. */\nfunction noise(seed: number, sample: number): number {\n  let x = (seed + Math.imul(sample, 0x6D2B79F5)) >>> 0;\n  x = Math.imul(x ^ x >>> 15, x | 1); x ^= x + Math.imul(x ^ x >>> 7, x | 61);\n  return ((x ^ x >>> 14) >>> 0) / 2147483648 - 1;\n}\n\nexport function renderPetPCM(voices: readonly PetVoice[], startSample: number, length: number, sampleRate = 48_000): { left: Float32Array; right: Float32Array } {\n  if (!Number.isInteger(sampleRate) || sampleRate < 8000 || sampleRate > 96000\n    || !Number.isSafeInteger(startSample) || startSample < 0 || !Number.isInteger(length) || length < 0 || length > sampleRate * 120)\n    throw new Error('Invalid pet PCM range.');\n  const left = new Float32Array(length), right = new Float32Array(length);\n  for (const v of voices) {\n    if (![v.start, v.duration, v.frequency, v.gain, v.pan].every(Number.isFinite)\n      || v.start < 0 || v.duration <= 0 || v.duration > 10 || v.frequency <= 0 || v.frequency > sampleRate / 2\n      || v.gain < 0 || v.gain > 1 || Math.abs(v.pan) > 1 || !['tone', 'noise'].includes(v.kind)) throw new Error('Invalid pet voice.');\n    const first = Math.max(startSample, Math.ceil(v.start * sampleRate));\n    const last = Math.min(startSample + length, Math.ceil((v.start + v.duration) * sampleRate));\n    const pan = (v.pan + 1) * Math.PI / 4, seed = (0xC0FFEE ^ stableHash(v.id)) >>> 0;\n    for (let absolute = first; absolute < last; absolute++) {\n      const age = absolute / sampleRate - v.start;\n      const envelope = Math.min(1, age / .015, (v.duration - age) / .045);\n      const sample = v.kind === 'noise' ? noise(seed, absolute) * Math.exp(-age * 14)\n        : Math.sin(2 * Math.PI * v.frequency * age) * .88 + Math.sin(4 * Math.PI * v.frequency * age) * .12;\n      const value = sample * Math.max(0, envelope) * v.gain, at = absolute - startSample;\n      left[at] += value * Math.cos(pan); right[at] += value * Math.sin(pan);\n    }\n  }\n  // Limiting is a presentation operation and cannot perturb voice scheduling.\n  for (let i = 0; i < length; i++) { left[i] = clamp(left[i], -1, 1); right[i] = clamp(right[i], -1, 1); }\n  return { left, right };\n}\n","sourceCodeStart":61,"sourceCodeEnd":96,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/src/core/pet-audio.ts#L61-L96","documentation":"renderPetPCM() validates every PetVoice before synthesis. A voice must have finite start, duration, frequency, gain, and pan, with start >= 0, 0 < duration <= 10, 0 < frequency <= sampleRate/2 (Nyquist), 0 <= gain <= 1, |pan| <= 1, and kind of 'tone' or 'noise'. This rejects voices that would produce non-finite samples or alias above Nyquist.","triggerScenarios":"Passing a voice array containing a voice with: a negative start; zero or negative duration; duration > 10 seconds; frequency <= 0 or above sampleRate/2 (e.g. 30 kHz tone at 48 kHz); gain outside 0–1; pan outside [-1, 1]; kind not 'tone'/'noise'; or any NaN/Infinity in the numeric fields (e.g. gain = v.gain * undefined).","commonSituations":"Generating voices with durations derived from user input that can exceed 10 s; synthesizing high-frequency alerts at low sample rates (Nyquist violation); gain left as a raw unmapped 0–255 value; kind typo like 'beep'; NaN propagating from a failed lookup used in frequency computation.","solutions":["Validate each voice before calling: finite numbers, start >= 0, 0 < duration <= 10, 0 < frequency <= sampleRate/2, 0 <= gain <= 1, |pan| <= 1, kind in ['tone','noise'].","Clamp on creation: duration = Math.min(dur, 10), gain = Math.max(0, Math.min(1, g)), pan = Math.max(-1, Math.min(1, p)).","Cap frequency at sampleRate/2 for the rate you will render at, not at an absolute constant.","Fix NaN sources: guard optional lookups feeding v.start/v.frequency so undefined arithmetic never reaches the voice.","Filter invalid voices out of the array before rendering instead of failing the whole batch."],"exampleFix":"// before\nconst pcm = renderPetPCM([{ id, start: -0.5, duration: 12, frequency: 30000, gain: 2, pan: 0, kind: 'beep' }], 0, 48000);\n\n// after\nconst voices = [{ id, start: Math.max(0, s), duration: Math.min(10, d),\n  frequency: Math.min(f, 48000 / 2), gain: clamp(g, 0, 1), pan: clamp(p, -1, 1), kind: 'tone' as const }];\nconst pcm = renderPetPCM(voices, 0, 48000);","handlingStrategy":"validation","validationCode":"function isValidVoice(v: PetVoice, sampleRate: number): boolean {\n  return [v.start, v.duration, v.frequency, v.gain, v.pan].every(Number.isFinite)\n    && v.start >= 0 && v.duration > 0 && v.duration <= 10\n    && v.frequency > 0 && v.frequency <= sampleRate / 2\n    && v.gain >= 0 && v.gain <= 1 && Math.abs(v.pan) <= 1\n    && ['tone', 'noise'].includes(v.kind);\n}\nconst safe = voices.filter(v => isValidVoice(v, 48000));","typeGuard":"function isPetVoice(v: unknown, sampleRate: number): v is PetVoice {\n  const w = v as PetVoice;\n  return !!w && typeof w.id === 'string'\n    && Number.isFinite(w.start) && w.start >= 0\n    && Number.isFinite(w.duration) && w.duration > 0 && w.duration <= 10\n    && Number.isFinite(w.frequency) && w.frequency > 0 && w.frequency <= sampleRate / 2\n    && Number.isFinite(w.gain) && w.gain >= 0 && w.gain <= 1\n    && Number.isFinite(w.pan) && Math.abs(w.pan) <= 1\n    && (w.kind === 'tone' || w.kind === 'noise');\n}","tryCatchPattern":"try {\n  const pcm = renderPetPCM(voices, start, length);\n} catch (err) {\n  if (err instanceof Error && err.message === 'Invalid pet voice.') {\n    console.error('dropping voice', voices); // inspect and fix producer\n    const pcm = renderPetPCM(voices.filter(v => isValidVoice(v, 48000)), start, length);\n  } else throw err;\n}","preventionTips":["Clamp gain and pan into [0,1] and [-1,1] at voice-creation time.","Cap voice duration at 10 s and cap frequency at sampleRate/2 for the target render rate.","Make kind a TypeScript union type ('tone' | 'noise') so typos fail at compile time.","Never let optional lookups feed numeric fields — coerce/guard against undefined to avoid NaN.","Filter voices through a validator before rendering rather than trusting producers."],"tags":["validation","audio","schema"],"backgroundTag":"invalid-argument-value","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}