jamiepine/voicebox · error · HTTPException

{validation error from validate_effects_chain}

Error message

{validation error from validate_effects_chain}

What it means

HTTP 400 raised by POST /effects/preview/{generation_id} when validate_effects_chain() returns a non-None error string. The validator checks: effects_chain is a list; each entry is a dict; the 'type' is in EFFECT_REGISTRY; 'params' is a dict; each param key is known; each param value is numeric and within [min, max]. The returned string identifies the first violation (e.g. unknown effect type, out-of-range param) and is passed verbatim as the 400 detail.

Source

Thrown at backend/routes/effects.py:38

    generation_id: str,
    data: models.ApplyEffectsRequest,
    db: Session = Depends(get_db),
):
    """Apply effects to a generation's clean audio and stream back without saving."""
    gen = db.query(DBGeneration).filter_by(id=generation_id).first()
    if not gen:
        raise HTTPException(status_code=404, detail="Generation not found")
    if (gen.status or "completed") != "completed":
        raise HTTPException(status_code=400, detail="Generation is not completed")

    from ..services import versions as versions_mod
    from ..utils.effects import apply_effects, validate_effects_chain
    from ..utils.audio import load_audio

    chain_dicts = [e.model_dump() for e in data.effects_chain]
    error = validate_effects_chain(chain_dicts)
    if error:
        raise HTTPException(status_code=400, detail=error)

    all_versions = versions_mod.list_versions(generation_id, db)
    clean_version = next((v for v in all_versions if v.effects_chain is None), None)
    source_path = clean_version.audio_path if clean_version else gen.audio_path
    resolved_source_path = config.resolve_storage_path(source_path)
    if resolved_source_path is None or not resolved_source_path.exists():
        raise HTTPException(status_code=404, detail="Source audio file not found")

    audio, sample_rate = await asyncio.to_thread(load_audio, str(resolved_source_path))
    processed = await asyncio.to_thread(apply_effects, audio, sample_rate, chain_dicts)

    import soundfile as sf

    buf = io.BytesIO()
    await asyncio.to_thread(lambda: sf.write(buf, processed, sample_rate, format="WAV"))
    buf.seek(0)

    return StreamingResponse(

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Read the detail string: it names the effect index, the param, and the allowed range.
  2. Cross-check effect types and param names against the EFFECT_REGISTRY (use the presets list or docs).
  3. Clamp numeric params to the registry min/max before submitting.
  4. Validate client-side with the same rules: list of dicts, known type, numeric in-range params.

Example fix

// before
const chain = [{ type: 'reverb', params: { room_size: 5.0 } }]; // out of range
// after
const chain = [{ type: 'reverb', params: { room_size: Math.min(1.0, Math.max(0.0, val)) } }];
await fetch(`/effects/preview/${genId}`, { method:'POST', body: JSON.stringify({ effects_chain: chain }) });
Defensive patterns

Strategy: validation

Validate before calling

function validateChain(chain, registry) {
  if (!Array.isArray(chain)) return 'must be a list';
  for (let i = 0; i < chain.length; i++) {
    const e = chain[i];
    if (!registry[e.type]) return `unknown type at ${i}`;
    for (const [k, v] of Object.entries(e.params || {})) {
      const def = registry[e.type].params[k];
      if (!def) return `unknown param ${k}`;
      if (typeof v !== 'number') return `${k} must be number`;
      if (v < def.min || v > def.max) return `${k} out of range`;
    }
  }
  return null;
}
const err = validateChain(chain, EFFECT_REGISTRY); if (err) throw new Error(err);

Type guard

function isValidEffect(e, registry): e is { type: string; params: Record<string, number> } {
  return !!registry[e?.type];
}

Try / catch

const r = await fetch(`/effects/preview/${id}`, { method:'POST', body: JSON.stringify(data) });
if (r.status === 400) { const { detail } = await r.json(); /* validator message names effect index + param */ }

Prevention

When it happens

Trigger: Submitting an effects_chain with an unknown effect type, an unknown parameter name, a non-numeric parameter, or a numeric parameter outside its declared min/max range.

Common situations: Hand-building a chain with a typo'd effect name; using a param valid in an older version that was renamed/removed; sending string values for numeric params; exceeding a gain/frequency bound.

Related errors


AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12). Data as JSON: /api/errors/36a578fe3349e707. Report an issue: GitHub.