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

  1. 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']
  2. Fix the cue generator so it never emits frequency >= sampleRate/2 (clamp to Nyquist)
  3. Correct the voice kind spelling to 'tone' or 'noise'
  4. 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

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


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)