{"record":{"id":"107c41ae83ba0245","repo":"Hmbown/CodeWhale","slug":"invalid-pet-pcm-range-audio","errorCode":null,"errorMessage":"Invalid pet PCM range.","messagePattern":"Invalid pet PCM range\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/src/core/pet-audio.ts","lineNumber":74,"sourceCode":"        }\n        if (frame.needs === 'call' && window % 10 === 0) add(`call:${window}`, CHANNELS[11].freq * 1.5, .38, .03);\n      }\n    }\n    return out;\n  }\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.","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/src/core/pet-audio.ts#L56-L92","documentation":"renderPetPCM() validates its range arguments before allocating buffers: sampleRate must be an integer in [8000, 96000], startSample a safe non-negative integer, and length a non-negative integer no larger than sampleRate*120 (two minutes of audio). This guard prevents allocating absurd buffers or synthesizing outside supported sample rates.","triggerScenarios":"Calling renderPetPCM(voices, startSample, length) with: a non-integer sampleRate (e.g. 44100.5) or one outside 8000–96000 (e.g. 192000), a negative or fractional startSample, a negative length, a non-integer length, or length exceeding sampleRate*120 (e.g. length=48_000*300 for a 5-minute render).","commonSituations":"Computing length from milliseconds with a unit error (ms instead of samples); using a device sample rate of 192 kHz; float drift from arithmetic like startSample = seconds * sampleRate producing 44099.999; exporting longer-than-2-minute clips.","solutions":["Log the three arguments at the call site and check them: integer sampleRate 8000–96000, integer startSample >= 0, integer 0 <= length <= sampleRate*120.","Clamp or validate duration before converting: length = Math.min(Math.floor(seconds * sampleRate), sampleRate * 120).","Use Math.round/Math.floor when deriving startSample and length from float seconds to avoid fractional values.","Pick a supported sample rate constant (e.g. 48_000) instead of reading an unrestricted device rate.","Wrap the call in try/catch and clamp to the nearest valid range on failure."],"exampleFix":"// before\nconst { left, right } = renderPetPCM(voices, t * sr, durationMs * sr, deviceRate);\n\n// after\nconst sr = 48_000;\nconst start = Math.max(0, Math.round(t * sr));\nconst len = Math.min(Math.max(0, Math.round(durationMs / 1000 * sr)), sr * 120);\nconst { left, right } = renderPetPCM(voices, start, len, sr);","handlingStrategy":"validation","validationCode":"function isPcmRange(startSample: number, length: number, sampleRate: number): boolean {\n  return Number.isInteger(sampleRate) && sampleRate >= 8000 && sampleRate <= 96000\n    && Number.isSafeInteger(startSample) && startSample >= 0\n    && Number.isInteger(length) && length >= 0 && length <= sampleRate * 120;\n}\nif (!isPcmRange(start, len, sr)) throw new Error('clamping needed');","typeGuard":"function isRenderablePcm(args: unknown): args is [number, number, number] {\n  return Array.isArray(args) && args.length === 3\n    && Number.isInteger(args[2]) && args[2] >= 8000 && args[2] <= 96000\n    && Number.isSafeInteger(args[0]) && args[0] >= 0\n    && Number.isInteger(args[1]) && args[1] >= 0 && args[1] <= args[2] * 120;\n}","tryCatchPattern":"try {\n  const pcm = renderPetPCM(voices, start, length, sampleRate);\n} catch (err) {\n  if (err instanceof Error && err.message === 'Invalid pet PCM range.') {\n    const sr = Math.min(96000, Math.max(8000, Math.round(sampleRate)));\n    const pcm = renderPetPCM(voices, Math.max(0, Math.round(start)), Math.min(length, sr * 120), sr);\n  } else throw err;\n}","preventionTips":["Derive sample counts with Math.round(seconds * sampleRate), never raw float math.","Enforce a max clip duration of 120 s at the product level.","Use fixed supported sample-rate constants (44100/48000); reject device rates outside 8000–96000.","Unit-test the boundary values: 8000, 96000, length = sampleRate*120, startSample = 0."],"tags":["validation","audio","range"],"backgroundTag":"argument-out-of-range","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"}