{"record":{"id":"7fe5e4dc997a1046","repo":"Hmbown/CodeWhale","slug":"invalid-pet-voice-native","errorCode":null,"errorMessage":"Invalid pet voice.","messagePattern":"Invalid pet voice\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/ios/Resources/pet-native.js","lineNumber":1630,"sourceCode":"}\nexports.PetScore = PetScore;\n/** Sample-addressed noise: no global RNG, identical samples when chunked/seeking. */\nfunction noise(seed, sample) {\n    let x = (seed + Math.imul(sample, 0x6D2B79F5)) >>> 0;\n    x = Math.imul(x ^ x >>> 15, x | 1);\n    x ^= x + Math.imul(x ^ x >>> 7, x | 61);\n    return ((x ^ x >>> 14) >>> 0) / 2147483648 - 1;\n}\nfunction renderPetPCM(voices, startSample, length, sampleRate = 48_000) {\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))\n            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 ^ (0, model_js_1.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);\n            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++) {\n        left[i] = (0, model_js_1.clamp)(left[i], -1, 1);\n        right[i] = (0, model_js_1.clamp)(right[i], -1, 1);\n    }","sourceCodeStart":1612,"sourceCodeEnd":1648,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/ios/Resources/pet-native.js#L1612-L1648","documentation":"The pet audio synthesizer validates each voice descriptor before rendering PCM samples. A voice must have finite start/duration/frequency/gain/pan within physical and safety bounds, and a kind of 'tone' or 'noise'; otherwise 'Invalid pet voice.' is thrown to prevent generating undefined or out-of-Nyquist-range audio.","triggerScenarios":"Calling the synth path with a voice whose duration exceeds 10s, frequency above sampleRate/2 or <= 0, gain outside 0..1, |pan| > 1, non-finite values (NaN/Infinity), or kind not 'tone'/'noise'.","commonSituations":"Config files with gain=1.5 or pan=-2, duration computed from a bad timer, NaN propagated from an earlier division, typos in kind (' Tone'), or a sample-rate change making a fixed frequency exceed Nyquist.","solutions":["Log the offending voice object and clamp/fix its fields before synthesis","Validate voice descriptors at the call site (start>=0, 0<duration<=10, 0<frequency<=sampleRate/2, 0<=gain<=1, |pan|<=1, kind in ['tone','noise'])","Check for NaN/Infinity earlier in the pipeline that computes the voice","Fix the kind string to exactly 'tone' or 'noise'"],"exampleFix":"// before\nconst voices = [{ start: 0, duration: 20, frequency: 440, gain: 1.5, pan: 0, kind: 'sine' }];\n// after\nconst voices = [{ start: 0, duration: 5, frequency: 440, gain: 1.0, pan: 0, kind: 'tone' }].map(v => ({\n  ...v,\n  duration: Math.min(v.duration, 10),\n  frequency: Math.min(v.frequency, sampleRate / 2),\n  gain: Math.max(0, Math.min(1, v.gain)),\n  kind: v.kind === 'noise' ? 'noise' : 'tone'\n}));","handlingStrategy":"validation","validationCode":"function isValidPetVoice(v, sampleRate) {\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}","typeGuard":"const isPetVoice = (v) => typeof v === 'object' && v !== null\n  && ['tone', 'noise'].includes(v.kind)\n  && [v.start, v.duration, v.frequency, v.gain, v.pan].every(Number.isFinite);","tryCatchPattern":"try {\n  synth(voices);\n} catch (err) {\n  if (err.message === 'Invalid pet voice.') {\n    const bad = voices.find(v => !isValidPetVoice(v, sampleRate));\n    console.error('dropping invalid voice', bad);\n    synth(voices.filter(isValidPetVoice));\n  } else throw err;\n}","preventionTips":["Clamp gain/pan/duration at voice-construction time, not at synthesis time","Compute frequencies from sampleRate so they never exceed Nyquist","Use a closed enum or union type for kind instead of free strings","Check for NaN early where values are computed (divisions, parses)"],"tags":["audio","validation","input-validation"],"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"}