Hmbown/CodeWhale · error · Error
Invalid pet voice.
Error message
Invalid pet voice.
What it means
renderPetPCM validates every voice descriptor before mixing it into the PCM buffers. It throws 'Invalid pet voice.' when any voice has a non-finite start/duration/frequency/gain/pan, a negative start, a duration outside (0, 10] seconds, a frequency outside (0, sampleRate/2] (Nyquist), gain outside [0, 1], |pan| > 1, or a kind other than 'tone' or 'noise'. Each voice is guarded individually so one bad cue cannot corrupt the whole render.
Solutions
- Validate each voice against the same predicate before calling: finite numbers, 0 < start, 0 < duration <= 10, 0 < frequency <= sampleRate/2, 0 <= gain <= 1, |pan| <= 1, kind in ['tone','noise']
- Fix the cue generator so it never emits frequency >= sampleRate/2 (clamp to Nyquist)
- Correct the voice kind spelling to 'tone' or 'noise'
- Filter out non-finite numbers at the source (skip or repair NaN durations/frequencies)
Example fix
// before
addVoice({ kind: 'sine', frequency: 26000, duration: 0, gain: 1.5, pan: 2 });
// after
addVoice({ kind: 'tone', frequency: Math.min(freq, sampleRate / 2 - 1), duration: Math.min(Math.max(dur, 0.01), 10), gain: clamp(gain, 0, 1), pan: clamp(pan, -1, 1) }); Defensive patterns
Strategy: validation
Validate before calling
function isValidVoice(v, sampleRate) {
return !!v && ['tone','noise'].includes(v.kind)
&& [v.start, v.duration, v.frequency, v.gain, v.pan].every(Number.isFinite)
&& v.start >= 0 && v.duration > 0 && v.duration <= 10
&& v.frequency > 0 && v.frequency <= sampleRate / 2
&& v.gain >= 0 && v.gain <= 1 && Math.abs(v.pan) <= 1;
}
voices = voices.filter(v => isValidVoice(v, sampleRate)); Type guard
const isVoice = (v) => typeof v === 'object' && (v.kind === 'tone' || v.kind === 'noise') && Number.isFinite(v.start) && Number.isFinite(v.duration) && Number.isFinite(v.frequency) && Number.isFinite(v.gain) && Number.isFinite(v.pan);
Try / catch
try {
return renderPetPCM(voices, start, len, sr);
} catch (err) {
if (err.message === 'Invalid pet voice.') return renderPetPCM(voices.filter(isValidVoiceCue), start, len, sr);
throw err;
} Prevention
- Clamp frequency to Nyquist (sampleRate/2) when generating cues
- Clamp duration into (0, 10] and gain into [0, 1] at cue creation
- Only emit kinds 'tone' and 'noise'; never ad-hoc kind strings
- Skip NaN-producing cue math instead of forwarding NaN
When it happens
Trigger: Passing a voice object where v.frequency is 0, NaN, or above sampleRate/2; v.duration is 0 or > 10; v.gain is negative or > 1; v.pan outside [-1, 1]; v.kind misspelled (e.g. 'sine' instead of 'tone'); or any numeric field missing/undefined so Number.isFinite fails.
Common situations: A scheduling bug emitting a voice with duration 0; a frequency derived from a sim channel that exceeds the new sampleRate after a rate change; a renamed voice kind after a refactor; NaN leaking in from division by zero in cue generation.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Invalid pet PCM range.
- Invalid pet PCM range.
- Invalid pet voice.
- Invalid pet voice.
- Model ' ' requires cloned voice data. Pass --clone-voice…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/170733e40e9a8762.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/tui/pet_watch/pet-native.js:1630
}
exports.PetScore = PetScore;
/** Sample-addressed noise: no global RNG, identical samples when chunked/seeking. */
function noise(seed, sample) {
let x = (seed + Math.imul(sample, 0x6D2B79F5)) >>> 0;
x = Math.imul(x ^ x >>> 15, x | 1);
x ^= x + Math.imul(x ^ x >>> 7, x | 61);
return ((x ^ x >>> 14) >>> 0) / 2147483648 - 1;
}
function renderPetPCM(voices, startSample, length, sampleRate = 48_000) {
if (!Number.isInteger(sampleRate) || sampleRate < 8000 || sampleRate > 96000
|| !Number.isSafeInteger(startSample) || startSample < 0 || !Number.isInteger(length) || length < 0 || length > sampleRate * 120)
throw new Error('Invalid pet PCM range.');
const left = new Float32Array(length), right = new Float32Array(length);
for (const v of voices) {
if (![v.start, v.duration, v.frequency, v.gain, v.pan].every(Number.isFinite)
|| v.start < 0 || v.duration <= 0 || v.duration > 10 || v.frequency <= 0 || v.frequency > sampleRate / 2
|| v.gain < 0 || v.gain > 1 || Math.abs(v.pan) > 1 || !['tone', 'noise'].includes(v.kind))
throw new Error('Invalid pet voice.');
const first = Math.max(startSample, Math.ceil(v.start * sampleRate));
const last = Math.min(startSample + length, Math.ceil((v.start + v.duration) * sampleRate));
const pan = (v.pan + 1) * Math.PI / 4, seed = (0xC0FFEE ^ (0, model_js_1.stableHash)(v.id)) >>> 0;
for (let absolute = first; absolute < last; absolute++) {
const age = absolute / sampleRate - v.start;
const envelope = Math.min(1, age / .015, (v.duration - age) / .045);
const sample = v.kind === 'noise' ? noise(seed, absolute) * Math.exp(-age * 14)
: Math.sin(2 * Math.PI * v.frequency * age) * .88 + Math.sin(4 * Math.PI * v.frequency * age) * .12;
const value = sample * Math.max(0, envelope) * v.gain, at = absolute - startSample;
left[at] += value * Math.cos(pan);
right[at] += value * Math.sin(pan);
}
}
// Limiting is a presentation operation and cannot perturb voice scheduling.
for (let i = 0; i < length; i++) {
left[i] = (0, model_js_1.clamp)(left[i], -1, 1);
right[i] = (0, model_js_1.clamp)(right[i], -1, 1);
}View on GitHub (pinned to 73e0f67d83)