foley#

foley — a retrieval-first façade for sound effects.

foley finds (or generates) the right sound effect for a moment of narration and weaves it in. It is the SFX sibling of arioso (a unified façade over AI music-generation backends): one simple surface over many sound sources (a bring-your-own library, service APIs like Freesound, and generative-AI models), a searchable index of every sound (by keyword and meaning, via CLAP embeddings + hybrid search), an agent that selects the right sound for a narrative context, and a compositor that places it under the voice.

Four stages:

SOURCE  ->  INDEX  ->  SELECT  ->  WEAVE
(get)      (find)      (choose)    (compose)

The façade (v1 — Epic #13 complete; the surface below is live — see misc/docs/design.md / misc/docs/roadmap.md for the roadmap):

import foley

foley.find("She pushed open the heavy oak door; rain hammered outside.")
foley.search("distant thunder rumble", k=10)
foley.generate("a single wooden door creak", backend="stable_audio")
foley.ingest("~/my_sounds/")

The design is grounded in the research reports under misc/docs/research/.

The whole four-stage surface (source → index → select → weave) plus the MCP server and the licensing/provenance, evaluation, and observability layers is implemented and re-exported here (see __all__ and the find / search / generate / ingest / weave façade functions below). The retrieval-agnostic foundation every later stage stands on:

  • Data models (foley.base) — the SSOT dataclasses/enums shared across layers (SoundRecord, LicenseRecord, Candidate, SoundEvent, Verdict, IntendedUse), the two

    affordance registries, and generic dict/JSON (de)serialization.

  • License policy (foley.licensing) — the license_id -> flag-set SSOT (LICENSE_FLAGS), flag derivation, and the fail-closed keep() gate.

  • Storage (foley.stores) — content-addressed byte store + metadata store built from dol, and store_sound() (the by-value vs by-reference gate driven by LicenseRecord.cache_bytes_ok).

  • QC (foley.qc) — Tier-0 deterministic audio checks (run_qc() -> QCReport, thresholds in QCThresholds).

  • Audio (foley.audio) — I/O + DSP primitives. Exposed as a submodule (foley.audio) with the key functions also re-exported here.

Import cost: import foley pulls only dol (a light core dependency used by foley.stores); numpy/soundfile/soxr/librosa/pyloudnorm are lazy-imported inside the audio/QC functions that need them (install via the foley[audio] extra), so a bare install imports cleanly.

Functions

resolve_master(master)

Resolve a master spec (profile name, explicit profile, or None) to a MasterProfile.

license_meta(license_id)

Return the display LicenseMeta for license_id (fail-closed fallback).

license_id_from_cc_url(url)

Map a Creative-Commons license URL or label to (license_id, verified).

derive_license_flags(license_id, *[, overrides])

Look up the flag set for a license_id (fail-closed fallback), then apply per-source overrides.

apply_license_flags(record, *[, overrides])

Populate record's eight derived flags from its license_id (+ overrides), in place, and return it.

keep(record, intended_use)

Fail-closed candidate license gate (report 07 §8.2).

keep_sound(sound_record, intended_use)

Convenience: apply keep() to a SoundRecord's nested license.

content_key(data, *[, algo])

Return the content-address key for data — its hex digest.

make_byte_store([rootdir])

Build the content-addressable blob store: Mapping[content_key -> bytes].

make_meta_store([rootdir])

Build the metadata store: Mapping[sound_id -> SoundRecord] (JSON files).

make_run_store([rootdir])

Build the run-artifact store: Mapping[run_id -> RunManifest dict] (JSON files).

store_sound(record[, data, cache_bytes_ok])

Persist a sound, choosing by-value vs by-reference from cache_bytes_ok.

run_qc(samples, sample_rate, *[, thresholds])

Run every Tier-0 check and fold the results into a QCReport.

has_nan_inf(samples)

Return True if any sample is NaN or Inf (corrupt-clip guard).

duration_s(samples, sample_rate)

Clip duration in seconds: frames / sample_rate.

dc_offset(samples)

Largest per-channel absolute DC offset, max_c |mean_n x[n, c]|.

is_silent(samples, *[, rms_floor_dbfs])

Return True when whole-clip RMS falls below rms_floor_dbfs.

detect_clipping(samples, *[, full_scale, ...])

Detect hard (flat-topped) clipping.

true_peak_dbtp(samples, sample_rate, *[, ...])

Inter-sample true-peak level in dBTP.

estimate_snr(samples, sample_rate, *[, ...])

Estimate SNR in dB (advisory — a busy-street SFX legitimately scores low).

needs_edge_fade(samples, *[, rel_peak_dbfs])

Return True when the first or last sample sits above rel_peak_dbfs relative to the clip peak — i.e. a nonzero boundary that clicks under narration and needs a short fade.

measure_lufs(samples, sample_rate, *[, ...])

Integrated loudness (LUFS, ITU-R BS.1770-4) via pyloudnorm (lazy).

load(src, *[, target_sr, mono, dtype])

Decode audio into a float working array.

save(samples, sample_rate, dst, *[, fmt, ...])

Write samples to dst as fmt/subtype (default = FLAC archive).

encode(samples, sample_rate, *[, fmt, subtype])

Encode samples fully in memory and return the container bytes.

resample(samples, sample_rate, *[, ...])

Resample samples to target_sr (a no-op when already there).

to_mono(samples)

Down-mix to mono by averaging channels; 1-D input passes through.

ensure_channels(samples, *, channels)

Coerce samples to exactly channels channels.

trim_silence(samples, sample_rate, *[, top_db])

Strip leading/trailing silence, returning the clip and its kept span.

fade(samples, sample_rate, *[, fade_in_s, ...])

Apply in/out gain ramps to samples (a short declick by default).

loudness_normalize(samples, sample_rate, *)

Loudness-normalize to target_lufs, then keep it peak-safe.

to_working(samples, sample_rate, *[, mono, ...])

Produce the canonical CLAP/QC working array from an arbitrary clip.

default_library()

The process-wide default library (local stores + CLAP + best index).

search(query, *[, k, filters, ...])

Hybrid (CLAP vector ⊕ BM25) search of the default library.

estimate(verb, **kwargs)

What a call would cost in USD before making it — None when it cannot be known (#57).

similar(sound_id, *[, k, commercial_ok])

Find sounds similar to a stored sound (audio<->audio) in the default library.

default_embedder()

Return a process-wide default ClapEmbedder (loaded once, reused).

default_index(*, data_dir, dim)

Build the best available persistent index for a library.

lancedb_available()

True if lancedb is importable (the foley[index] extra is present).

sqlite_vec_loadable()

True if sqlite_vec is installed AND this interpreter can load it.

hybrid_search(query, *, embedder, vindex, kindex)

Embed query, run the vector + keyword rankers, and RRF-fuse them.

vector_search(qvec, *, vindex[, k, where])

Pure audio<->audio (or clip->library) vector search — no keyword leg.

reciprocal_rank_fusion(ranked_id_lists, *[, k])

Fuse several ranked id lists into one, by reciprocal rank.

fuse_hits(vector_hits, keyword_hits, *, k[, ...])

RRF-fuse a vector ranker's hits with a keyword ranker's hits.

resolve_catid(*[, tags, caption, ...])

Resolve inputs to a best UCS CatID by the staged precedence.

parse_ucs_filename(filename, *[, table])

Parse a UCS-conformant filename to (ucs_category, ucs_subcategory).

default_tagger()

The default supervised tagger (PANNs CNN14; foley[tag]).

default_zeroshot_tagger([embedder])

The default zero-shot tagger (CLAP vs UCS subcategories; foley[clap]).

ingest(path, *[, library, backend, qc, ...])

Ingest a folder (or single file) of sounds into the default library.

restamp_rights([library, license, ids, ...])

Re-stamp the licence of stored sounds; a dry run unless apply=True.

ingest_one(src, *[, library, sound_id, ...])

Ingest one clip into library and return an IngestResult.

ingest_folder(path, *[, library, recursive, ...])

Ingest every audio file under path and return an IngestReport.

bootstrap(*[, rings, corpora, data_dir, ...])

Seed library from the selected bulk corpora, returning per-corpus reports.

demo(*[, library, query, k])

Ingest the bundled Ring-0 fixture and run one search — the smoke test.

add_from(source, *, query[, license, limit, ...])

Search a live source and ingest its license-clean hits into library.

list_sources(*[, egress_allow])

Return the names of registered live sources (runs discovery first).

register_source(name, config[, adapter])

Register a live source directly (out-of-tree plugin or a test double).

generate(prompt, *[, backend, library, ...])

Generate a sound effect for prompt and add it to the library (by-value).

candidate_of(result)

Wrap a stored IngestResult as a generated candidate.

art50_checklist(record)

Return the per-clip EU AI Act Art.

scan_prompt(prompt)

Scan a generation prompt for trademarked-audio-logo + voice-clone risk.

find(context, *[, max_events, seconds, ...])

The headline: a narrative context → verified, license-clean sound candidates.

plan(candidates, *[, transcript])

Fold verified candidates into the SPARSE SoundDesignTimeline (the SELECT→WEAVE bridge).

decompose_context(context, *[, max_events, ...])

Decompose a passage into <= max_events sparse SoundEvents.

refine_query(query, *[, n, hint, refiner, _span])

Expand query into up to n paraphrases for multi-query retrieval.

verify_match(event, candidate, *[, level, ...])

Verify candidate against event up to rung level (AND-confirming ladder).

decide(event, kept, verified, *, ...)

The single generate-vs-retrieve branch — a PURE function (report 05 §4).

score(segments, *[, audio, transcript, ...])

Choose sounds for narration text and (optionally) weave them into the narration audio.

install_agent_kit([dest, overwrite])

Copy the shipped skill + slash command + subagent into dest (a .claude dir).

mcp_server(*[, library, session, runtime, ...])

Build the foley MCP server (lazy py2mcp); registers the JSON-safe tool surface.

build_mcp_server(*[, library, session, ...])

Build the foley MCP server (lazy py2mcp); registers the JSON-safe tool surface.

make_http_app(*, auth[, path, ...])

Build a bearer-auth-gated ASGI app serving the foley MCP tools over streamable HTTP.

serve_http(*[, host, port, path])

Build and serve the foley MCP tools over authenticated streamable HTTP (blocks).

preview(candidate_or_id, *[, seconds, ...])

Produce a short audition of a sound; set its preview_uri to a store key.

similar_to(clip_or_candidate, *[, k, library])

"More like this" — neighbours of a sound id / candidate, or of a raw clip.

refine([session, query, picked_ids, ...])

Relevance-feedback refinement: expand for recall, boost picks, drop rejects, re-rank.

make_session_store([session_id, name, rootdir])

Build a per-session JSON store: Mapping[key -> dict] under sessions/{id}/{name}/.

offline([config])

Alias of offline_scope() — with foley.offline(): ... for local-first runs.

is_offline()

Whether an offline runtime scope is currently active.

using_source_accounts(**accounts)

Bind per-request source accounts (a key, and what goes with it) for a with block.

check_requirements(*[, names, verbose])

Report which optional foley capabilities are available ({name: is_available}).

verify_and_setup(*[, names])

Return a per-requirement status + guidance report (never runs an installer).

capability_report(*[, runtime])

A JSON-safe capability + posture snapshot for the CLI, docs, and the MCP tool.

evaluate(*[, golden, k])

Run the Tier-1 retrieval eval over the golden set (nDCG@10 / recall / mAP / MRR).

evaluate_fit(*[, golden, sample, level, ...])

Run the Tier-2 fit eval over the golden set — "does the accepted clip fit?" (#10b).

credits(sounds, *[, title, only_required, ...])

Build the TASL attribution Credits for sounds.

credits_for(sounds, *[, title, ...])

Build the deduplicated Credits for the sounds used in a run.

attribution_line(source, *[, fmt])

Render one sound's TASL attribution line.

credit_entry(record, *[, title])

Build a CreditEntry from a record (title override optional).

Classes

StorageMode(*values)

How a sound's bytes are held (DERIVED from license.cache_bytes_ok).

AcquisitionMethod(*values)

How a sound entered foley (retrieval channel or origin).

CandidateOrigin(*values)

Whether a candidate was retrieved from the index or freshly generated.

Salience(*values)

How prominent a sound event is within a passage.

Layer(*values)

Mix layer (shared by SoundEvent now and TimelineItem later).

VerifyLevel(*values)

Which rung of the verification ladder produced a Verdict.

Affordance(name, type, description[, ...])

Descriptor for a unified parameter affordance (arioso analog).

SerializableMixin()

Adds to_dict/to_json/from_dict/from_json to a dataclass.

LicenseRecord(source[, source_id, ...])

Per-sound rights + provenance.

SoundRecord(id[, content_sha256, hash_algo, ...])

Canonical SSOT per sound.

SoundEvent(query[, layer, diegetic, ...])

One salient, physically-audible event decomposed from a passage.

Verdict(match, confidence[, reason, level])

The result of one verification rung for a candidate.

Candidate(sound[, origin, event, ...])

A ranked, license-checked, (optionally) verified sound for one SoundEvent.

IntendedUse([commercial, publish, ...])

What the caller intends to do with a sound; consumed by keep().

TimelineItem(clip_ref[, onset, gain, layer, ...])

One placed sound on the sound-design timeline — sparse seed + resolved render fields.

SoundDesignTimeline([items, ...])

The editable, re-renderable sound-design plan — the SELECT→WEAVE bridge and render SSOT.

Anchor(*values)

How a WEAVE Placement binds its symbolic time to the narration (report 06 §2.4).

Placement([anchor, ref, onset, pre_roll, ...])

WHERE/WHEN a clip sits — a symbolic anchor plus its resolved time (report 06 §6.3).

Processing([gain_db, pan, distance, ...])

HOW a clip sounds — all optional with identity defaults (report 06 §3, §6.3).

MasterProfile([target_lufs, true_peak_db, lra])

Loudness master target — the delivery spec as data, not code (report 06 §5.2).

LicenseFlags([commercial_ok, ...])

The eight derivable flags for one license_id (the table row type).

LicenseMeta(display_name[, url])

Human-facing display metadata for one license_id (name + canonical URL).

QCStatus(*values)

Overall verdict for a clip (subclasses str so it is JSON-safe).

QCThresholds([clip_full_scale, ...])

All Tier-0 QC defaults (report 08 §3 table), each an explicit field.

QCReport(duration_s, sample_rate, channels, ...)

Per-clip Tier-0 QC result.

SoundLibrary(*[, sounds, meta, vindex, ...])

A searchable, license-aware library of sounds (the foley INDEX façade).

Embedder(*args, **kwargs)

A joint text<->audio embedding space (CLAP by default).

ClapEmbedder([model_id, device])

LAION-CLAP text<->audio embedder (the default retrieval engine).

VectorIndex(*args, **kwargs)

Approximate-nearest-neighbour store over embedding vectors.

KeywordIndex(*args, **kwargs)

BM25 / full-text index over each sound's tags + caption.

MemoryIndex(*[, dim])

In-memory vector + keyword index (numpy cosine + compact BM25).

LanceIndex(*, uri, dim[, table_name])

LanceDB-backed index: one table with a vector column + a native FTS index.

SqliteVecIndex(*, path, dim)

Single-file index: sqlite-vec vec0 KNN + stdlib FTS5 keyword search.

FusedHit(id[, rrf_score, clap_score, bm25_score])

One fused retrieval hit: an id plus the scores that produced it.

CatIdResolution([catid, category, ...])

The result of resolving free tags/caption/labels to a UCS CatID.

Tagger(*args, **kwargs)

Map a clip to (label, score) pairs against a label vocabulary.

Captioner(*args, **kwargs)

Produce one natural-language sentence describing a clip (report 03).

ClapZeroShotTagger(*[, embedder, labels, ...])

Zero-shot tagger: score a clip against a label set via CLAP cosine.

PannsTagger(*[, device, threshold])

PANNs CNN14 supervised tagger over the 527 AudioSet classes (foley[tag]).

IngestResult(id, status[, record, qc, ...])

The outcome of ingesting one clip.

IngestReport(root[, results, notes])

The rolled-up outcome of a folder ingest (JSON-serializable).

RunManifest(run_id, op[, created_at, ...])

The reproducible run-artifact for one foley operation (trace ⊕ plan ⊕ provenance).

SpanRecord(name, span_id[, parent_id, kind, ...])

One node of the run's span tree (the trace half of the artifact).

Judge(*args, **kwargs)

One rung of the verify ladder: does this candidate match this event? (report 10 §4.2).

Budget([max_refine_loops, max_generations, ...])

Bounded-cost accounting: per-event loop counts, and a cumulative dollar cap (#57).

Decision(action[, candidate, reason])

The tiny result of decide(); reason feeds the refine hint + the audit Step.

WeaveResult(audio, sr, timeline, credits, ...)

The output of weave() — the v1 Definition-of-Done deliverable.

ScoreResult(timeline, events[, weave])

The output of score() — the editable plan + per-event rationale (+ mix when woven).

ScoredEvent(segment, query, sound_id, ...)

One chosen sound placed for a narration event (a JSON-friendly rationale row).

SessionStore([session_id, candidates, ...])

Three namespaced stores for one audition session (candidates / picks / rejects).

RuntimeConfig([offline, data_egress_allow, ...])

A frozen runtime posture — the local-first / offline contract as data.

Credits([entries, title, schema_version])

A deduplicated, ordered collection of CreditEntry for one run.

CreditEntry(sound_id[, title, author, ...])

One rendered TASL credit for a single sound (a flat, serializable row).

Exceptions

GenerationError(message, *, report, status)

Raised by foley.generate() when a backend yields no stored sound.

SafetyRefusal(message, *, hits, report)

Raised (fail-closed) when a generation prompt trips a #9b safety gate.

TrademarkRefusal(message, *, hits, report)

A SafetyRefusal for a prompt naming a trademarked audio logo (report 07 §7.2).

RecognizableVoiceRefusal(message, *, hits, ...)

A SafetyRefusal for a prompt requesting a recognizable / cloned voice (report 07 §7.1).

class foley.AcquisitionMethod(*values)[source]#

Bases: str, Enum

How a sound entered foley (retrieval channel or origin).

class foley.Affordance(name, type, description, default=None, stage='query', carries_meaning=False)[source]#

Bases: object

Descriptor for a unified parameter affordance (arioso analog).

name#

Canonical parameter name used at the façade level.

type#

Expected Python type.

description#

Human-readable description.

default#

Default value (None = no default / required).

stage#

'query' (search/find/filter) or 'generate'.

carries_meaning#

Dropping it would change what the caller gets (a seed they will rely on to reproduce, content they asked to exclude), so a backend that cannot honour it raises instead of dropping it (#53).

class foley.Anchor(*values)[source]#

Bases: str, Enum

How a WEAVE Placement binds its symbolic time to the narration (report 06 §2.4).

absolute is a fixed offset; the rest resolve against the forced-aligned word_timeline — word to a trigger word’s onset, sentence across a sentence span, scene/paragraph to a boundary’s first spoken word.

class foley.Budget(max_refine_loops=1, max_generations=1, allow_generate=True, max_usd=1.0, approve_unknown_cost=None, _refines=0, _gens=0, _spent_usd=0.0, _unknown_calls=0, _lock=<factory>)[source]#

Bases: object

Bounded-cost accounting: per-event loop counts, and a cumulative dollar cap (#57).

Two kinds of bound:

  • Per event — max_refine_loops / max_generations: the loop calls refine_ok() / gen_ok() to test, then spend_refine() / spend_gen() to charge; reset() zeroes them at each event.

  • Per run, cumulative — max_usd (default $1): every paid call (generation, paid LLM rung) is checked with authorize() before it is made and recorded with charge() after. reset() never clears it, so the cap holds across the whole find / score run. A call whose cost is unknown (estimate None) is refused unless approve_unknown_cost (or $FOLEY_APPROVE_UNKNOWN_COST=1).

authorize(estimate_usd, *, what)[source]#

Alias of check() (kept for callers that only test affordability).

Return type:

None

charge(usd)[source]#

Record a call’s cost (None = unknown: counted, not summed).

Return type:

None

check(estimate_usd, *, what)[source]#

Raise if this run cannot afford estimate_usd (nothing is reserved).

Return type:

None

gen_ok()[source]#

Whether a generation fallback is allowed and within budget.

Return type:

bool

refine_ok()[source]#

Whether another refine→re-retrieve pass is within budget.

Return type:

bool

reserve(estimate_usd, *, what)[source]#

Check, then count estimate_usd as spent at once (atomic under a lock).

Reserving before the call is what keeps concurrent calls on one budget (MCP tools run in a threadpool) from all passing the check before any is charged. settle() later replaces the reservation with the actual cost, if known.

Return type:

None

reset()[source]#

Zero the spend counters so the caps apply per event, not per passage.

The find loop calls this at the top of each event so one hard event’s refine/generate spend never starves later events (the documented per-event semantics).

Return type:

None

settle(reserved_usd, actual_usd)[source]#

Replace a reservation with the actual cost (None actual: keep the reservation).

Return type:

None

spend_gen()[source]#

Charge one generation.

Return type:

None

spend_refine()[source]#

Charge one refine loop.

Return type:

None

property spent_usd: float#

Dollars charged so far this run (calls of unknown cost count as 0 here).

class foley.Candidate(sound, origin=CandidateOrigin.retrieved, event=None, clap_score=None, bm25_score=None, rrf_score=None, rerank_score=None, verdict=None, license_ok=None, preview_uri=None, notes=<factory>, cost_estimate_usd=None, cost_actual_usd=None)[source]#

Bases: SerializableMixin

A ranked, license-checked, (optionally) verified sound for one SoundEvent.

Retrieval and generation return the SAME shape; only origin differs. Nested sound / event / verdict dataclasses are decoded generically by _decode() — no per-field code needed.

class foley.CandidateOrigin(*values)[source]#

Bases: str, Enum

Whether a candidate was retrieved from the index or freshly generated.

class foley.Captioner(*args, **kwargs)[source]#

Bases: Protocol

Produce one natural-language sentence describing a clip (report 03).

The caption feeds the BM25 keyword index and human display. Default is a dedicated AAC model (EnCLAP); Qwen2-Audio is a richer promptable upgrade. Both are foley[caption] adapters plugged in behind this protocol.

caption(wav, sr)[source]#

Return a one-sentence caption for the clip.

Return type:

str

class foley.CatIdResolution(catid=None, category=None, subcategory=None, source=None, confidence=0.0, matched_terms=<factory>)[source]#

Bases: object

The result of resolving free tags/caption/labels to a UCS CatID.

catid feeds ucs_category and subcategory feeds ucs_subcategory on ingest, and ucs_catid on the query side.

class foley.ClapEmbedder(model_id='laion/larger_clap_general', *, device=None)[source]#

Bases: object

LAION-CLAP text<->audio embedder (the default retrieval engine).

Returns L2-normalized float32 embeddings so a plain inner product is cosine similarity. embed_text always returns a 2-D (n, dim) array; embed_audio returns a 1-D (dim,) array for one clip.

model_id#

The HF checkpoint id.

dim#

The embedding dimensionality.

open_source#

Whether the checkpoint is an open-source model (the LAION releases are). Read by the ingest AI-use gate for sounds whose rights holder allows only open-source models (#69).

property device: str#

The resolved torch device string ('cuda'/'cpu').

property dim: int#

The embedding dimensionality (512 for the default; resolved for others).

For a non-default checkpoint this fetches only the model’s config.json (via AutoConfig) — never the ~1.7 GB weights — so building an index for it does not force a model download. Falls back to the loaded model’s config if the standalone config lacks projection_dim.

embed_audio(wav, sr)[source]#

Embed one audio clip -> (dim,) L2-normalized.

The clip is down-mixed to mono and resampled to 48 kHz (what CLAP expects) via foley.audio before embedding.

Parameters:
  • wav (ndarray) – A working-array clip (float32; mono or multichannel).

  • sr (int) – The clip’s sample rate in Hz.

Return type:

ndarray

embed_text(text)[source]#

Embed one or more query strings -> (n_texts, dim) L2-normalized.

Return type:

ndarray

class foley.ClapZeroShotTagger(*, embedder=None, labels=None, prompt='this is a sound of {label}', threshold=0.0)[source]#

Bases: object

Zero-shot tagger: score a clip against a label set via CLAP cosine.

Reuses a ClapEmbedder (the same model the Index uses), so the audio is embedded in the same joint space as the label prompts and no extra weights load. The default label set is the UCS subcategory names (foley’s own vocabulary), so tags land in-taxonomy.

property embedder#

The CLAP embedder (injected or the process-wide default).

property labels: list[str]#

natural UCS category subcategory phrases).

Natural multi-word phrases ("weather rain", "glass break") are far better CLAP prompts — and better BM25 tags / taxonomy-resolver input — than bare abstract subcategory words ("Break", "Buzz"), which are a known zero-shot artifact (anomalously close to everything). Absolute tag-quality calibration (thresholds, label curation) is an eval-harness concern (#10).

Type:

The label vocabulary (default

tag(wav, sr, *, taxonomy='custom', top_k=10)[source]#

Return the top-k (label, cosine) tags for the clip, best first.

Return type:

list[tuple[str, float]]

tag_vector(audio_vec, *, top_k=10)[source]#

Score a precomputed (L2-normalized) audio vector against the labels.

The efficiency seam (report 03 Part 2): the Index already embeds every sound with this model, so on ingest the retrieval vector is reused here — no second CLAP forward pass.

Return type:

list[tuple[str, float]]

class foley.CreditEntry(sound_id, title=None, author=None, author_url=None, source=None, source_url=None, license_id='unknown', license_name=None, license_url=None, modified=False, requires_attribution=False, attribution_text=None, notice_text_required=None, is_ai_generated=False, generator_model=None, disclosure_recommended=False, watermark=None, c2pa_manifest_ref=None)[source]#

Bases: SerializableMixin

One rendered TASL credit for a single sound (a flat, serializable row).

Built by credit_entry() from a record’s rights fields; rendered to a single attribution line by attribution_line(). Carries the AI-disclosure

  • watermark / C2PA fields as pass-throughs so the JSON manifest becomes the content-credentials carrier once #6/#9b populate them (None today).

class foley.Credits(entries=(), title='Credits', schema_version=1)[source]#

Bases: SerializableMixin

A deduplicated, ordered collection of CreditEntry for one run.

Iterable and sized; renders to CREDITS.md via markdown and to a JSON manifest via manifest (== to_dict()). Both are deterministic (no timestamps) and diffable.

property manifest: dict#

The machine-readable JSON manifest (a plain dict).

property markdown: str#

The rendered CREDITS.md document.

class foley.Decision(action, candidate=None, reason='')[source]#

Bases: object

The tiny result of decide(); reason feeds the refine hint + the audit Step.

class foley.Embedder(*args, **kwargs)[source]#

Bases: Protocol

A joint text<->audio embedding space (CLAP by default).

One space serves both text->audio search (embed_text a query) and audio<->audio similarity (embed_audio a clip). Implementations MUST return L2-normalized float32 arrays so a plain inner product is cosine similarity, and MUST stamp model_id/dim so mixed-model libraries stay coherent (each SoundRecord records the embedding_model/embedding_dim it was indexed under).

model_id#

The checkpoint id (e.g. 'laion/larger_clap_general').

dim#

The embedding dimensionality (e.g. 512).

embed_audio(wav, sr)[source]#

Embed one audio clip.

Parameters:
  • wav (ndarray) – A working-array clip (float32, mono preferred). CLAP expects 48 kHz; implementations resample as needed.

  • sr (int) – The clip’s sample rate in Hz.

Return type:

ndarray

Returns:

A 1-D (dim,) L2-normalized float32 array.

embed_text(text)[source]#

Embed one or more query strings.

Parameters:

text (Union[str, list[str]]) – A single string or a list of strings.

Return type:

ndarray

Returns:

A 2-D (n_texts, dim) L2-normalized float32 array (n_texts is 1 for a single string) — always 2-D so callers can index [0] for the single-query case.

class foley.FusedHit(id, rrf_score=None, clap_score=None, bm25_score=None)[source]#

Bases: object

One fused retrieval hit: an id plus the scores that produced it.

The raw component scores are carried through (not just the fused rank score) so the façade can stamp them onto a Candidate (clap_score / bm25_score / rrf_score) for display and debugging.

id#

The sound id.

rrf_score#

The fused RRF score (None for a pure-vector search).

clap_score#

Cosine similarity from the vector ranker (None if the id appeared only in the keyword list).

bm25_score#

BM25 score from the keyword ranker (None if the id appeared only in the vector list).

exception foley.GenerationError(message, *, report, status)[source]#

Bases: RuntimeError

Raised by foley.generate() when a backend yields no stored sound.

Carries the full IngestReport and the terminal status so a caller can react distinctly to quarantined (QC-rejected — e.g. regenerate), rights_blocked, or error. The lower-level generate() workhorse never raises this — it always returns an inspectable report; only the public foley.generate() promise raises.

report#

The IngestReport from the run.

status#

The terminal IngestResult status ('quarantined' | 'rights_blocked' | 'error' | …).

class foley.IngestReport(root, results=<factory>, notes=<factory>)[source]#

Bases: SerializableMixin

The rolled-up outcome of a folder ingest (JSON-serializable).

error(path, exc)[source]#

Record a per-file error without aborting the run.

Return type:

None

property errored: list[IngestResult]#

Results that raised during ingest.

exception = None#

The exception behind the last error result, kept so a raising façade can chain it (raise ... from); a plain attribute, never serialized.

property ingested: list[IngestResult]#

Results that were added to the library (pass or warn).

property quarantined: list[IngestResult]#

Results rejected by the QC gate.

record(result)[source]#

Append one IngestResult.

Return type:

None

property rights_blocked: list[IngestResult]#

Results refused by the fail-closed AI-training/license rights gate.

property skipped: list[IngestResult]#

Results skipped as content-addressed duplicates.

summary()[source]#

A counts dict for a console/CLI summary.

Return type:

dict

class foley.IngestResult(id, status, record=None, qc=None, notes=<factory>, error=None, cost_estimate_usd=None, cost_actual_usd=None)[source]#

Bases: SerializableMixin

The outcome of ingesting one clip.

status: 'pass'/'warn' (ingested), 'quarantined' (QC-rejected, not added), 'skipped_dup' (content already in the library), 'rights_blocked' (license forbids AI training / embedding, refused before embed — see ingest_one()), 'skipped_license' (dropped by a bootstrap commercial-use / fail-closed license filter), or 'error'. record is present only when the clip was ingested.

class foley.IntendedUse(commercial=True, publish=True, redistribute_standalone=False, will_train=False, can_attribute=True, revenue_usd=0, allow_voice_or_trademark=False)[source]#

Bases: SerializableMixin

What the caller intends to do with a sound; consumed by keep().

class foley.Judge(*args, **kwargs)[source]#

Bases: Protocol

One rung of the verify ladder: does this candidate match this event? (report 10 §4.2).

level selects the rung — clap (cheap score gate), listen (audio-LM), judge (LLM arbitration + scene consistency). The returned Verdict carries level == the rung that produced it. Only the judge rung’s real impl calls the LLM.

class foley.KeywordIndex(*args, **kwargs)[source]#

Bases: Protocol

BM25 / full-text index over each sound’s tags + caption.

The default is LanceDB’s Tantivy FTS (report 04 §3.4); SQLite FTS5 is the single-file fallback. Same where push-down contract as VectorIndex.

bm25(query, k, *, where=None)[source]#

Return the top-k BM25 matches for query, best first.

Parameters:
  • query (str) – A natural-language / keyword query.

  • k (int) – Number of matches to return.

  • where (Optional[dict]) – Optional metadata predicates for push-down filtering.

Return type:

list[tuple[str, float]]

Returns:

[(id, bm25_score), ...] in descending-score order.

index(id, text, meta)[source]#

Insert or replace the searchable text (and light metadata) for id.

Return type:

None

class foley.LanceIndex(*, uri, dim, table_name='sounds')[source]#

Bases: object

LanceDB-backed index: one table with a vector column + a native FTS index.

Writes are staged per id and flushed on the next read (or explicit commit()), so a sound’s vector (upsert()) and text (index()) — which arrive as two separate protocol calls — are merged into one row and written as an efficient batch. Vector search is exact cosine (no ANN index is built at this tier; adding one is a scale-time optimization). Fusion is done by foley.index.search, not by LanceDB’s native hybrid reranker, so ranking matches every other backend.

One-table constraint: the vector column is mandatory, so **every indexed row needs a vector**. SoundLibrary.add enforces this (it raises without an embedding source), so the façade path is safe; a bare index() with no matching upsert() stages a text-only row that stays unflushed (never keyword-searchable). For a keyword-only library with no embeddings, use MemoryIndex or SqliteVecIndex (independent vector/text tables).

bm25(query, k, *, where=None)[source]#

Native full-text (BM25) search; returns [(id, score), ...].

Return type:

list[tuple[str, float]]

commit()[source]#

Flush all staged writes to the LanceDB table.

Return type:

None

property db#

The lazily-connected LanceDB database handle.

get_vector(id)[source]#

Return the stored vector for id (staged or persisted), else None.

Return type:

Optional[ndarray]

index(id, text, meta)[source]#

Stage the searchable text for id (flushed on the next read/commit).

Return type:

None

knn(vector, k, *, where=None)[source]#

Exact cosine KNN; returns [(id, cosine_similarity), ...].

Return type:

list[tuple[str, float]]

property table#

The lazily-opened (or created-empty) LanceDB table.

upsert(id, vector, meta)[source]#

Stage the vector for id (flushed on the next read/commit).

Return type:

None

class foley.Layer(*values)[source]#

Bases: str, Enum

Mix layer (shared by SoundEvent now and TimelineItem later).

class foley.LicenseFlags(commercial_ok=False, embed_in_derivative_ok=False, redistribute_standalone_ok=False, cache_bytes_ok=False, modification_ok=False, ai_training_ok=False, requires_attribution=False, revenue_cap_usd=None)[source]#

Bases: object

The eight derivable flags for one license_id (the table row type).

class foley.LicenseMeta(display_name, url=None)[source]#

Bases: object

Human-facing display metadata for one license_id (name + canonical URL).

The presentation sibling of LicenseFlags: where LicenseFlags holds the permission row consulted by keep(), LicenseMeta holds the display row consulted by the credits/attribution layer (foley.provenance.credits). Kept here so licensing stays the single license authority; a record’s own license_name / license_url (when a source populated them) take precedence over this default.

class foley.LicenseRecord(source, source_id=None, source_url=None, acquisition_method=AcquisitionMethod.user, retrieved_at=None, adapter_version=None, content_sha256=None, license_id='unknown', license_name=None, license_version=None, license_url=None, rights_holder=None, creator_name=None, creator_url=None, commercial_ok=False, embed_in_derivative_ok=True, redistribute_standalone_ok=False, cache_bytes_ok=False, modification_ok=False, ai_training_ok=False, ai_training_scope=None, gen_ai_preference=None, revenue_cap_usd=None, requires_attribution=False, attribution_text=None, notice_text_required=None, transformations=<factory>, is_ai_generated=False, generator_model=None, generator_version=None, generation_prompt=None, generation_seed=None, generation_params=<factory>, watermark=None, c2pa_manifest_ref=None, contains_recognizable_voice=False, potential_trademark=False, disclosure_recommended=False, rights_verified=False, verified_at=None, schema_version=1)[source]#

Bases: SerializableMixin

Per-sound rights + provenance. SSOT for BOTH keep() and storage mode.

The derived permission flags default fail-closed here (the bare-record baseline) — with one deliberate exception: embed_in_derivative_ok defaults True (the normal case for a licensed sound). That is not a live bypass: keep() checks rights_verified first, so an unverified record is rejected regardless. Populate the flags from the license_id via foley.licensing.apply_license_flags (source overrides win). Never hand-set the derived flags — always route through the policy layer.

class foley.MasterProfile(target_lufs=-16.0, true_peak_db=-1.0, lra=11.0)[source]#

Bases: SerializableMixin

Loudness master target — the delivery spec as data, not code (report 06 §5.2).

class foley.MemoryIndex(*, dim=None)[source]#

Bases: object

In-memory vector + keyword index (numpy cosine + compact BM25).

Not persistent — everything lives in dicts, lost on process exit. It exists so the full hybrid + façade path is testable and usable with only numpy (no LanceDB, no torch), and as a genuine zero-config tier for small or ephemeral libraries. The vector and text stores are independent dicts, so upsert() and index() never contend.

bm25(query, k, *, where=None)[source]#

Return the top-k BM25 matches for query (best first).

A compact Okapi BM25 (k1=1.5, b=0.75) recomputed per query — O(N) in the corpus size, which is fine for the in-memory tier’s scale.

Return type:

list[tuple[str, float]]

commit()[source]#

No-op (writes are immediate); present for interface symmetry.

Return type:

None

get_vector(id)[source]#

Return the stored vector for id (or None).

Return type:

Optional[ndarray]

index(id, text, meta)[source]#

Insert or replace the searchable text (and light metadata) for id.

Return type:

None

knn(vector, k, *, where=None)[source]#

Return the k cosine-nearest ids to vector (most-similar first).

Return type:

list[tuple[str, float]]

upsert(id, vector, meta)[source]#

Insert or replace the vector (and light metadata) for id.

Return type:

None

class foley.PannsTagger(*, device='cpu', threshold=0.1)[source]#

Bases: object

PANNs CNN14 supervised tagger over the 527 AudioSet classes (foley[tag]).

The checkpoint auto-downloads to ~/panns_data (~327 MB) on the first tag(). PANNs expects 32 kHz mono; the clip is resampled via foley.audio.to_working().

tag(wav, sr, *, taxonomy='audioset', top_k=10)[source]#

Return the top-k (AudioSet label, score) tags, best first.

Return type:

list[tuple[str, float]]

class foley.Placement(anchor=Anchor.absolute, ref=None, onset=0.0, pre_roll=0.0, duration=None, loop=False)[source]#

Bases: SerializableMixin

WHERE/WHEN a clip sits — a symbolic anchor plus its resolved time (report 06 §6.3).

Filled by WEAVE’s aligner+anchor pass. onset is the resolved start in seconds (distinct from the sparse TimelineItem.onset symbolic string); pre_roll shifts the clip earlier so its salient transient — not its file start — lands on the anchor (report 06 §2.4).

class foley.Processing(gain_db=0.0, pan=0.0, distance=0.0, reverb_send=0.0, fade_in=0.008, fade_out=0.012, duck_bed=False)[source]#

Bases: SerializableMixin

HOW a clip sounds — all optional with identity defaults (report 06 §3, §6.3).

Every field is a no-op at its default, so a sparse item (no processing) renders untouched; the mixer departs from dry/centered/full-level only when a field is set.

class foley.QCReport(duration_s, sample_rate, channels, clipped_ratio, clipped_max_run, dc_offset, rms_dbfs, is_silent, needs_edge_fade, has_nan_inf, true_peak_dbtp=None, snr_db=None, loudness_lufs=None, status=QCStatus.pass_, notes=<factory>)[source]#

Bases: object

Per-clip Tier-0 QC result.

Mirrors the fields the SoundRecord schema already carries (duration_s, sample_rate, channels, loudness_lufs) plus the deterministic check outputs, an overall status, and human-readable notes for every firing condition. Serialize with to_dict() into SoundRecord.qc.

to_dict()[source]#

Return a plain, JSON-safe dict (status as its string value).

Non-finite floats are swept to None at this boundary too (belt-and- suspenders over the construction-time _json_safe() guard) so no Infinity/NaN can ever reach json.dumps regardless of who set a field.

Return type:

dict

class foley.QCStatus(*values)[source]#

Bases: str, Enum

Overall verdict for a clip (subclasses str so it is JSON-safe).

class foley.QCThresholds(clip_full_scale=0.999, clip_min_run=3, clip_reject_ratio=0.0001, clip_reject_run=10, true_peak_max_dbtp=-1.0, true_peak_oversample=4, dc_offset_fail=0.01, dc_offset_warn=0.001, silence_rms_dbfs=-60.0, snr_clean_db=20.0, snr_quiet_percentile=10.0, snr_frame_s=0.025, snr_hop_s=0.01, edge_rel_peak_dbfs=-40.0, edge_fade_s=0.01, lufs_gate_floor=-70.0, lufs_outlier_lu=6.0, duration_min_s=0.1, deliver_min_sample_rate=44100)[source]#

Bases: object

All Tier-0 QC defaults (report 08 §3 table), each an explicit field.

Grouped by check. Pass a customized instance to run_qc() (or the per-check keyword arguments) to override any threshold without editing code.

exception foley.RecognizableVoiceRefusal(message, *, hits, report)[source]#

Bases: SafetyRefusal

A SafetyRefusal for a prompt requesting a recognizable / cloned voice (report 07 §7.1).

class foley.RunManifest(run_id, op, created_at=None, foley_version=None, inputs=<factory>, params=<factory>, spans=<factory>, steps=<factory>, ingest_report=None, result_ids=<factory>, candidate_scores=<factory>, credits_ref=None, disclosure_refs=<factory>, seeds=<factory>, plan_ref=None, trace_ref=None, status='ok', error=None, schema_version=1)[source]#

Bases: SerializableMixin

The reproducible run-artifact for one foley operation (trace ⊕ plan ⊕ provenance).

Persisted by emit_run_manifest() into a run store keyed by run_id. Sensitive prompt/query text lives redacted (see foley.obs.redact); chosen clips are held by reference (SoundRecord id) so the manifest stays light and a trace can be replayed against a fresh index.

class foley.RuntimeConfig(offline=False, data_egress_allow=<factory>, telemetry=True, redaction_mode='hash', http_resilience=True)[source]#

Bases: object

A frozen runtime posture — the local-first / offline contract as data.

Parameters:
  • offline (bool) – Whether this posture is offline/local-first.

  • data_egress_allow (frozenset[str]) – The egress classes a source may use to be available ({'local'} offline; {'local','external'} online).

  • telemetry (bool) – Whether the obs run-artifact export is on.

  • redaction_mode (str) – 'hash' (default, salted), 'off' (drop), or 'full' (raw — local-debug only).

  • http_resilience (bool) – Whether HTTP source adapters are wrapped with the throttle/backoff/circuit-breaker (foley.sources.resilience).

allows(data_egress)[source]#

Whether a source declaring data_egress is available under this posture.

An unknown/absent declaration is rejected (fail-closed): a source that does not say where its data goes is never used offline.

Return type:

bool

classmethod default()[source]#

The online default: all egress allowed, telemetry on, hashed redaction.

Return type:

RuntimeConfig

classmethod from_env()[source]#

Build from the environment: FOLEY_OFFLINE in {1,true,yes} → offline-local.

Return type:

RuntimeConfig

classmethod offline_local()[source]#

The local-first offline posture: local-only egress, telemetry off, hashed redaction.

Return type:

RuntimeConfig

exception foley.SafetyRefusal(message, *, hits, report)[source]#

Bases: GenerationError

Raised (fail-closed) when a generation prompt trips a #9b safety gate.

A pre-synthesis hard stop: nothing is generated or stored. Distinct from a resilience error result (a backend/ingest failure the workhorse records without raising) — a safety refusal is a deliberate refusal of an unsafe request. Its report is an empty pre-generation report and status is 'refused'. Carries hits (the matched marks/patterns). Subclass of GenerationError so the public foley.generate() Raises clause already covers it. See foley.provenance.disclosure.scan_prompt().

class foley.Salience(*values)[source]#

Bases: str, Enum

How prominent a sound event is within a passage.

class foley.ScoreResult(timeline, events, weave=None)[source]#

Bases: object

The output of score() — the editable plan + per-event rationale (+ mix when woven).

property n_sounds: int#

How many sounds were placed (the restraint check — fewer than one-per-sentence).

property rationale: str#

A short, agent/human-readable summary of what was chosen and why.

class foley.ScoredEvent(segment, query, sound_id, origin, confidence, reason)[source]#

Bases: object

One chosen sound placed for a narration event (a JSON-friendly rationale row).

to_dict()[source]#

Plain-dict form (for the MCP projection / a caller’s log).

Return type:

dict

class foley.SerializableMixin[source]#

Bases: object

Adds to_dict/to_json/from_dict/from_json to a dataclass.

Stdlib-only, DRY, no-magic (de)serialization. Every SSOT dataclass below inherits this instead of hand-rolling per-class encoders/decoders.

classmethod from_dict(d)[source]#

Reconstruct an instance from a plain dict.

Enum fields and nested dataclasses are coerced via _decode(). Unknown keys are ignored (forward-compatible); missing keys fall back to field defaults.

Parameters:

d (dict) – A plain dict (typically from to_dict() or json.loads).

Return type:

SerializableMixin

classmethod from_json(s)[source]#

Reconstruct an instance from a JSON string.

Parameters:

s (str) – A JSON string (typically from to_json()).

Return type:

SerializableMixin

to_dict()[source]#

Return the recursive plain-dict form.

Enum members are preserved (and remain JSON-safe because every enum subclasses str); nested dataclasses are recursed via dataclasses.asdict.

Return type:

dict

to_json(*, indent=None)[source]#

Return a JSON string (str-enums serialize to their .value).

Parameters:

indent (Optional[int]) – Optional pretty-print indent passed to json.dumps.

Return type:

str

class foley.SessionStore(session_id='default', candidates=None, picks=None, rejects=None)[source]#

Bases: object

Three namespaced stores for one audition session (candidates / picks / rejects).

Each store defaults to a foley.stores.make_session_store() JSON store; tests inject plain dicts. All values are JSON-safe dicts.

Parameters:
  • session_id (str) – The session namespace.

  • rejects (Optional[dict]) – Optional injected MutableMapping stores.

add_pick(sound_id, *, layer=None, onset=None)[source]#

Persist an accepted pick (+ optional layer/onset); return the pick count.

Return type:

int

add_reject(sound_id, *, reason=None)[source]#

Record a rejected sound (feeds refine relevance feedback); return the count.

Return type:

int

cache_candidates(candidates)[source]#

Cache each candidate’s full to_dict() keyed by sound id; return the count cached.

Return type:

int

drop_pick(sound_id)[source]#

Remove a pick (idempotent); return the remaining pick count.

Return type:

int

list_picks()[source]#

All persisted picks.

Return type:

list[dict]

list_rejects()[source]#

All recorded rejects.

Return type:

list[dict]

picked_ids()[source]#

The picked sound ids.

Return type:

list[str]

rehydrate(ids)[source]#

Rebuild Candidate objects for ids from the cache.

Missing ids are skipped. Uses Candidate.from_dict (rebuilds the nested SoundRecord / LicenseRecord / Verdict).

Return type:

list[Candidate]

rejected_ids()[source]#

The rejected sound ids.

Return type:

list[str]

class foley.SoundDesignTimeline(items=<factory>, run_manifest_ref=None, transcript_ref=None, schema_version=1, narration_ref=None, word_timeline=<factory>, master=<factory>)[source]#

Bases: SerializableMixin

The editable, re-renderable sound-design plan — the SELECT→WEAVE bridge and render SSOT.

SELECT (#7) emits the SPARSE form (just items + the run/transcript joins) via foley.agent.plan(). WEAVE (#8) grows it additively: narration_ref binds the voice audio, word_timeline caches the forced alignment (the reproducible seed), master carries the loudness target, and each item gains its resolved Placement/Processing. render(timeline, library) is then a PURE function of this data + the library, so editing any field and re-rendering reproduces exactly that change. run_manifest_ref == foley.obs.RunManifest.run_id (the reserved #8 plan_ref join).

class foley.SoundEvent(query, layer=Layer.sfx_fg, diegetic=True, salience=Salience.medium, onset=None, loop=False, ucs_catid=None, audioset=<factory>, era_place=None)[source]#

Bases: SerializableMixin

One salient, physically-audible event decomposed from a passage.

class foley.SoundLibrary(*, sounds=None, meta=None, vindex=None, kindex=None, embedder=None, data_dir=None, candidate_k=50, rrf_k=60)[source]#

Bases: Mapping

A searchable, license-aware library of sounds (the foley INDEX façade).

Read it as a Mapping of SoundRecord`s; search it with :meth:`search (text) / search_clip() (a reference clip) / similar() (audio<->audio by id); browse it with filter(); grow it with add().

add(record, *, data=None, vector=None)[source]#

Store a sound and index it (the ingest write path).

Persists bytes via store_sound() (honouring the by-value/by-reference license gate), upserts the CLAP vector into the vector index, and indexes caption``+``tags into the keyword index.

A sound is retrieval-first, so it MUST carry an embedding: supply either data (bytes to embed — note a by-reference sound is embedded from its transient bytes even though they are not cached) or a precomputed vector. Adding with neither raises, rather than silently indexing a vectorless row (which the single-table LanceIndex cannot persist, producing backend-dependent search results).

Parameters:
  • record (SoundRecord) – The record to add (mutated by store_sound with resolved storage fields, and stamped with the embedding model/dim).

  • data (Optional[bytes]) – The archive bytes (required for by-value storage; also the source for computing vector when it is not supplied).

  • vector (Optional[ndarray]) – A precomputed CLAP embedding; when omitted and data is given, it is computed via the library’s embedder.

Return type:

SoundRecord

Returns:

The same (persisted, indexed) record.

Raises:

ValueError – If neither data nor vector is provided (no way to obtain an embedding).

array(sound_id, *, sr=None, mono=True)[source]#

Decode a sound to a working array (float32).

Parameters:
  • sound_id (str) – The record id.

  • sr (Optional[int]) – Target sample rate (default: the working rate, 48 kHz).

  • mono (bool) – Down-mix to mono (default True).

Return type:

ndarray

Returns:

The decoded working array.

audio(sound_id)[source]#

Return a sound’s archive bytes (by-value from the store, or from a local by-reference path).

Parameters:

sound_id (str) – The record id.

Return type:

bytes

Returns:

The raw archive bytes.

Raises:

LookupError – If the bytes are neither cached (by-value) nor readable from a local uri — a remote by-reference sound needs its source adapter (subtask #5) to fetch.

property data_dir: Path#

The data root for default stores/index.

property embedder#

The text<->audio embedder (CLAP by default).

filter(**facets)[source]#

Browse the library by metadata facets (no ranking).

Accepts the same facet keywords as search()’s filters (commercial_ok, ucs_category, min_snr, duration_range) plus any record_attr=value equality predicate.

Return type:

list[SoundRecord]

property kindex#

The keyword index.

property meta#

The metadata store (id -> SoundRecord).

search(query, *, k=10, filters=None, commercial_ok=None, ucs_category=None, min_snr=None, duration_range=None, rerank=False)[source]#

Hybrid (CLAP vector ⊕ BM25) search for a text query.

Parameters:
  • query (str) – The natural-language query.

  • k (int) – Number of results to return.

  • filters (Optional[dict]) – Extra {record_attr: value} equality predicates.

  • commercial_ok (Optional[bool]) – If True, keep only commercially-usable sounds.

  • ucs_category (Optional[str]) – Keep only sounds with this UCS CatID.

  • min_snr (Optional[float]) – Keep only sounds whose QC snr_db is at least this.

  • duration_range (Optional[tuple[float, float]]) – Keep only sounds whose duration_s is in (min, max).

  • rerank (bool) – Re-order the shortlist by direct query<->audio cosine (fills the CLAP score for keyword-only hits).

Return type:

list[Candidate]

Returns:

Up to k :class:`~foley.base.Candidate`s, best first.

search_clip(clip, *, sr=None, k=10)[source]#

Search by a reference audio clip (audio<->audio via CLAP).

Parameters:
Return type:

list[Candidate]

Returns:

Up to k :class:`~foley.base.Candidate`s, most-similar first.

similar(sound_id, *, k=10)[source]#

Return the k sounds most similar to sound_id (audio<->audio).

Uses the stored vector (no re-decoding); the query sound itself is excluded from the results.

Return type:

list[Candidate]

property sounds#

The content-addressed byte store.

update_record(record)[source]#

Rewrite a stored record’s metadata and re-index its keyword text.

For corrections that do not touch the audio (a re-stamped licence, a removed caption): the bytes and the CLAP vector stay as they are.

Raises:

KeyError – If record.id is not in the library.

Return type:

SoundRecord

property vindex#

The vector index.

class foley.SoundRecord(id, content_sha256=None, hash_algo='sha256', uri=None, storage_mode=StorageMode.by_reference, archive_format=None, source_sample_rate=None, source_bit_depth=None, license=<factory>, caption=None, tags=<factory>, ucs_category=None, ucs_subcategory=None, audioset_labels=<factory>, duration_s=None, sample_rate=None, channels=None, loudness_lufs=None, format=None, qc=None, embedding_model=None, embedding_dim=None, embedding_ref=None, named_cue=None, schema_version=1)[source]#

Bases: SerializableMixin

Canonical SSOT per sound.

Audio bytes + CLAP vector live in SEPARATE stores keyed by the same id; this record holds a content-hash uri, never raw bytes.

class foley.SpanRecord(name, span_id, parent_id=None, kind=None, start_ms=None, duration_ms=None, status='ok', attributes=<factory>, events=<factory>, error=None)[source]#

Bases: SerializableMixin

One node of the run’s span tree (the trace half of the artifact).

Built by the recorder from its own clock + id-factory, independent of any tracer, so the tree is complete even when the OTel mirror is a total no-op.

class foley.SqliteVecIndex(*, path, dim)[source]#

Bases: object

Single-file index: sqlite-vec vec0 KNN + stdlib FTS5 keyword search.

The whole index is one SQLite file behind two virtual tables (independent, so upsert() and index() never contend). **Requires an interpreter whose sqlite3 permits loadable extensions** — probe with sqlite_vec_loadable() before constructing; the constructor raises a clear error otherwise.

bm25(query, k, *, where=None)[source]#

FTS5 BM25 search; returns [(id, score), ...] best-first.

FTS5’s rank is more-negative-is-better; it is negated so the returned score is larger-is-better (consistent with the other backends).

Return type:

list[tuple[str, float]]

close()[source]#

Close the underlying SQLite connection.

Return type:

None

commit()[source]#

Commit any pending SQLite transaction (writes auto-commit already).

Return type:

None

get_vector(id)[source]#

Return the stored vector for id (or None).

Return type:

Optional[ndarray]

index(id, text, meta)[source]#

Insert or replace the searchable text for id in the FTS5 table.

Return type:

None

knn(vector, k, *, where=None)[source]#

KNN over vec0 (cosine); returns [(id, cosine_similarity), ...].

Return type:

list[tuple[str, float]]

upsert(id, vector, meta)[source]#

Insert or replace the vector for id in the vec0 table.

Return type:

None

class foley.StorageMode(*values)[source]#

Bases: str, Enum

How a sound’s bytes are held (DERIVED from license.cache_bytes_ok).

class foley.Tagger(*args, **kwargs)[source]#

Bases: Protocol

Map a clip to (label, score) pairs against a label vocabulary.

Used on ingest to auto-fill SoundRecord.audioset_labels (supervised) and tags (zero-shot). The default supervised tagger is PANNs CNN14 over AudioSet; the default zero-shot tagger scores a clip against a custom/UCS label set via CLAP (report 03). BEATs/AST are drop-in upgrades. Consumes the working array (float32, any sr — the impl resamples to its model’s rate).

tag(wav, sr, *, taxonomy='audioset', top_k=10)[source]#

Return the top-k (label, score) tags for the clip, best first.

Return type:

list[tuple[str, float]]

class foley.TimelineItem(clip_ref, onset=None, gain=0.0, layer=Layer.sfx_fg, loop=False, id=None, placement=None, processing=None, event=None, enabled=True)[source]#

Bases: SerializableMixin

One placed sound on the sound-design timeline — sparse seed + resolved render fields.

SELECT (#7) sets only the sparse flat fields (clip_ref·onset·gain·layer·loop); WEAVE (#8) additively fills id / placement / processing (and may carry the originating event for provenance). enabled is a non-destructive mute. The sparse flat fields are never removed — they stay SELECT’s SSOT input; the render reads the resolved placement/processing (falling back to the flat fields when those are absent).

exception foley.TrademarkRefusal(message, *, hits, report)[source]#

Bases: SafetyRefusal

A SafetyRefusal for a prompt naming a trademarked audio logo (report 07 §7.2).

class foley.VectorIndex(*args, **kwargs)[source]#

Bases: Protocol

Approximate-nearest-neighbour store over embedding vectors.

The default is LanceDB (report 04 §2); Qdrant/pgvector/sqlite-vec bind the same protocol behind the scenes. where is an optional metadata push-down the façade may pass; a backend that cannot push filters down MAY ignore it (the façade over-fetches and post-filters to stay correct either way).

get_vector(id)[source]#

Return the stored vector for id (or None if absent).

Needed by SoundLibrary.similar (fetch a sound’s own vector, then run knn()) and by the optional CLAP rerank (score keyword-only hits).

Return type:

Optional[ndarray]

knn(vector, k, *, where=None)[source]#

Return the k nearest ids to vector, most-similar first.

Parameters:
  • vector (ndarray) – A (dim,) query vector (already L2-normalized).

  • k (int) – Number of neighbours to return.

  • where (Optional[dict]) – Optional metadata predicates for push-down filtering.

Return type:

list[tuple[str, float]]

Returns:

[(id, cosine_similarity), ...] in descending-similarity order.

upsert(id, vector, meta)[source]#

Insert or replace the vector (and light metadata) for id.

Return type:

None

class foley.Verdict(match, confidence, reason='', level=VerifyLevel.clap)[source]#

Bases: SerializableMixin

The result of one verification rung for a candidate.

class foley.VerifyLevel(*values)[source]#

Bases: str, Enum

Which rung of the verification ladder produced a Verdict.

class foley.WeaveResult(audio, sr, timeline, credits, captions_vtt, captions_srt, master_report, run_manifest_ref=None, content_credential=None, watermark=None, notes=<factory>)[source]#

Bases: object

The output of weave() — the v1 Definition-of-Done deliverable.

A mastered mix + an editable, re-renderable timeline + credits + SDH captions + a reproducible run-artifact join, plus the fail-safe provenance re-assertion.

notes: list#

What the caller should know about how the mix was made, e.g. that word timings are estimates because no forced aligner is installed (#65).

foley.add_from(source, *, query, license='cc0', limit=50, library=None, intended_use=None, adapter=None, on_unsupported=None, **affordances)[source]#

Search a live source and ingest its license-clean hits into library.

Progressive disclosure: add_from("freesound", query="ocean waves") works out of the box (CC0-only, into the process-wide default library); every other knob is an optional keyword. Each hit is license-gated BEFORE any bytes are fetched (fail-closed), then routed through ingest_one(), which applies the by-reference storage gate from the sound’s own license.

Parameters:
  • source (str) – A registered live-source name (e.g. 'freesound').

  • query (str) – The natural-language search query.

  • license (Optional[str]) – License constraint pushed into the source query (default 'cc0'). The per-item fail-closed guard enforces the source’s accepted-license allowlist regardless.

  • limit (int) – Max candidates to request from the source.

  • library – Target SoundLibrary (default: the process-wide default library).

  • intended_use (Optional[IntendedUse]) – The rights intent each candidate is gated against (default: DEFAULT_INTENDED_USE).

  • adapter – An optional pre-built adapter to use instead of the registry’s (the dependency-injection seam — a test passes a fake-transport adapter; production omits it and the registry lazily builds one).

  • on_unsupported (Optional[str]) – A search parameter the source cannot honour: None (default) drops it with a note in report.notes; 'raise' raises (see foley.sources._dispatch.translate_affordances()).

  • **affordances – Extra unified affordances forwarded to the adapter’s search (e.g. duration_range, sort).

Return type:

IngestReport

Returns:

An IngestReport — inspect .ingested for the stored records (each storage_mode == by_reference for Freesound) and .summary() for counts, exactly like foley.ingest().

foley.apply_license_flags(record, *, overrides=None)[source]#

Populate record’s eight derived flags from its license_id (+ overrides), in place, and return it.

Does NOT touch rights_verified — verification is a separate concern.

Parameters:
Return type:

LicenseRecord

Returns:

The same (mutated) record.

foley.attribution_line(source, *, fmt='markdown')[source]#

Render one sound’s TASL attribution line.

A source-supplied attribution_text (if non-empty) is returned verbatim; otherwise the line is synthesized from Title/Author/Source/License, with a (modified) notice and an AI-disclosure segment appended as applicable.

Parameters:
Return type:

str

Returns:

The attribution line (no leading bullet / trailing newline).

foley.bootstrap(*, rings=(0, 1), corpora=None, data_dir=None, library=None, roots=None, accept_ai_restricted=False, commercial_only=None, **ingest_one_kw)[source]#

Seed library from the selected bulk corpora, returning per-corpus reports.

Parameters:
  • rings (tuple[int, ...]) – Which rings to include (default (0, 1) — Ring 2 is never default; it is opt-in via corpora=[...] + accept_ai_restricted).

  • corpora (Optional[list[str]]) – Explicit corpus-name allowlist; overrides rings when given.

  • data_dir (Optional[str]) – Root under which each corpus lives at data_dir/<name> (default: the library’s data dir / $FOLEY_DATA_DIR).

  • library – Target SoundLibrary (default: the process-wide default library).

  • roots (Optional[dict[str, str]]) – Optional per-corpus root overrides ({name: path}) — for corpora downloaded somewhere other than data_dir/<name>.

  • accept_ai_restricted (bool) – Consent gate for Ring-2 / ai_training_ok=False corpora. False (default) refuses them; True records explicit operator consent and admits them.

  • commercial_only (Optional[bool]) – Force the per-clip commercial filter on/off. None (default) derives it from the ring (Ring 1 → on, else off).

  • **ingest_one_kw – Forwarded to foley.index.ingest.ingest_one() (do_supervised, do_zeroshot, min_status, thresholds …).

Return type:

dict[str, IngestReport]

Returns:

{corpus_name: IngestReport} — inspect each .summary().

foley.build_mcp_server(*, library=None, session='default', runtime=None, byte_store=None, include=None, name='foley', max_usd=None)[source]#

Build the foley MCP server (lazy py2mcp); registers the JSON-safe tool surface.

Validates that every source declares a data_egress (fail-closed), binds the injectable library / runtime / byte-store, and hands the resolved tool functions to py2mcp.mk_mcp_server. Never starts a server or touches the network.

Parameters:
Returns:

A fastmcp.FastMCP server.

foley.candidate_of(result)[source]#

Wrap a stored IngestResult as a generated candidate.

The report-10 §4.2 shape: retrieval and generation return the same Candidate, differing only in origin. Use on a pass / warn result (its record is the canonical, stored SoundRecord).

Parameters:

result (IngestResult) – A stored ingest result (result.record is not None).

Return type:

Candidate

Returns:

A Candidate with origin=CandidateOrigin.generated.

foley.capability_report(*, runtime=None)[source]#

A JSON-safe capability + posture snapshot for the CLI, docs, and the MCP tool.

Groups requirements into keys (env), extras (importable), system (binary), adds the current offline posture and the available source list, and lists degraded_tools — capabilities whose requirement is unmet.

Parameters:

runtime – A foley.runtime.RuntimeConfig (default: the active one).

Return type:

dict

Returns:

{keys, extras, system, offline, sources, llm, degraded_tools} — all JSON-safe. llm is the provider the SELECT rungs resolve to right now (see foley.agent.llm.resolve_llm()), or the reason it cannot resolve.

foley.check_requirements(*, names=None, verbose=False)[source]#

Report which optional foley capabilities are available ({name: is_available}).

Parameters:
  • names (tuple[str, ...] | None) – Which requirements to check (default: the full assembled set).

  • verbose (bool) – If True, print an actionable hint for each missing requirement.

Return type:

dict[str, bool]

Returns:

{requirement_name: available}. Everything-absent is fine — foley degrades (deterministic fakes, offline mode, in-process DSP); the report just shows what each capability would unlock.

foley.content_key(data, *, algo='sha256')[source]#

Return the content-address key for data — its hex digest.

Using the hash as the key gives free deduplication (identical bytes map to the same key) and immutability (a key always names the exact same bytes).

Parameters:
  • data (bytes) – The raw bytes to address (e.g. a FLAC archive blob).

  • algo (str) – A hashlib algorithm name (defaults to HASH_ALGO).

Return type:

str

Returns:

The lowercase hex digest of data under algo.

foley.credit_entry(record, *, title=None)[source]#

Build a CreditEntry from a record (title override optional).

Every field is read straight off the LicenseRecord (flags never re-derived); modified reflects a non-empty transformations list.

Parameters:
Return type:

CreditEntry

foley.credits(sounds, *, title='Credits', only_required=False, write_to=None)[source]#

Build the TASL attribution Credits for sounds.

Works standalone today (given any iterable of sounds), and is what the WEAVE stage will call at render time. Inspect .markdown (a CREDITS.md document) / .manifest (a JSON-serializable dict) on the result.

Parameters:
  • sounds – An iterable of SoundRecord / Candidate / LicenseRecord (e.g. the result of search(), or the sounds placed in a timeline).

  • title (str) – The credits heading.

  • only_required (bool) – Keep only legally-required attributions (drops CC0 / user-owned courtesy credits). Default credits everything.

  • write_to – Optional directory; when given, writes CREDITS.md and credits.json into it (created if missing).

Returns:

A Credits.

foley.credits_for(sounds, *, title='Credits', only_required=False, sort='appearance')[source]#

Build the deduplicated Credits for the sounds used in a run.

Parameters:
  • sounds (Iterable[Union[SoundRecord, Candidate, LicenseRecord]]) – An iterable of records / candidates / license records.

  • title (str) – The credits heading (also carried in the manifest).

  • only_required (bool) – Keep only entries whose license requires attribution (drops CC0 / user-owned courtesy credits). Default False credits everything (never-discard-provenance).

  • sort (str) – 'appearance' (default: first-seen order), 'author', or 'title' (case-insensitive alpha).

Return type:

Credits

Returns:

A Credits; identical sounds (same id) are credited once (first-writer-wins).

foley.dc_offset(samples)[source]#

Largest per-channel absolute DC offset, max_c |mean_n x[n, c]|.

Return type:

float

foley.decide(event, kept, verified, *, tau_retrieve, budget, loop)[source]#

The single generate-vs-retrieve branch — a PURE function (report 05 §4).

Chooses among DecideAction from the already-gated (kept) and already-verified (verified) sets. It performs no I/O and never calls keep/search/generate — the foley.agent.tools loop acts on the returned Decision.

Policy (report 05 §4):

  • a verified clip clearing tau_retrieve → USE (the best one);

  • verified-but-low-confidence with refine budget → REFINE (feed the reason back);

  • a non-diegetic cue, or a diegetic gap with no verified match, with generate budget → GENERATE;

  • otherwise → DROP (silence), unless a lower-confidence verified clip exists and generation is off, in which case fall back to that best-effort pick.

Parameters:
  • event (SoundEvent) – The event being resolved.

  • kept (list[Candidate]) – The license-clean candidates (each license_ok is True).

  • verified (list[Candidate]) – The subset of kept whose verdict matched.

  • tau_retrieve (float) – The confidence threshold for auto-accepting a retrieved clip.

  • budget (Budget) – The per-event cost budget.

  • loop (int) – The current refine-loop index (for the audit reason).

Return type:

Decision

Returns:

A Decision.

foley.decompose_context(context, *, max_events=6, seconds=None, decomposer=None, _span=None)[source]#

Decompose a passage into <= max_events sparse SoundEvents.

The pure SELECT tool (Python-API == agent == future-MCP surface): resolves the default decomposer when decomposer is None, calls it, and records the GenAI span on the real path (the fake’s last_response is None → no-op).

Parameters:
  • context (str) – The narrative passage.

  • max_events (int) – The sparse density cap.

  • seconds (Optional[float]) – Optional passage duration (density-window hint; forwarded, else ignored).

  • decomposer (Optional[Decomposer]) – An injected Decomposer (the DI seam); defaults to _default_decomposer().

  • _span – Internal — the obs span handle find() opens for GenAI recording.

Return type:

list[SoundEvent]

foley.default_embedder()[source]#

Return a process-wide default ClapEmbedder (loaded once, reused).

Cached so repeated foley.search() calls share a single loaded model.

Return type:

ClapEmbedder

foley.default_index(*, data_dir, dim)[source]#

Build the best available persistent index for a library.

Degradation ladder: LanceDB (foley[index]) → sqlite-vec (foley[index-sqlite], if loadable) → an informative error. The non-persistent MemoryIndex is never chosen automatically (a library must survive restarts); inject it explicitly for tests/ephemeral use.

Parameters:
  • data_dir – The library data root (a pathlib.Path-like).

  • dim (int) – The embedding dimensionality from the active embedder.

Returns:

A ready index object (both VectorIndex and KeywordIndex).

Raises:

RuntimeError – If no persistent backend is installed/usable.

foley.default_library()[source]#

The process-wide default library (local stores + CLAP + best index).

Return type:

SoundLibrary

foley.default_tagger()[source]#

The default supervised tagger (PANNs CNN14; foley[tag]).

Return type:

PannsTagger

foley.default_zeroshot_tagger(embedder=None)[source]#

The default zero-shot tagger (CLAP vs UCS subcategories; foley[clap]).

Cached per embedder so the tagger is bound to the SAME embedder that produced the audio vector it scores — otherwise (report seam) the cosine would cross two unrelated embedding spaces. embedder=None uses the process-wide default embedder.

Return type:

ClapZeroShotTagger

foley.demo(*, library=None, query='rain on a window', k=3)[source]#

Ingest the bundled Ring-0 fixture and run one search — the smoke test.

Needs no corpus download (the fixture ships in the wheel), but does need the runtime extras: foley[audio] to decode and, unless a library is injected, foley[clap] for the default embedder (its model downloads on first use). Uses an ephemeral in-memory library (so it never mutates $FOLEY_DATA_DIR) unless one is injected. The fixture’s per-clip captions + tags (from its manifest.json) flow into the keyword index so a plain-text query resolves.

Parameters:
  • library – Optional target library (tests inject a FakeEmbedder one; the default builds a memory library with the real CLAP embedder).

  • query (str) – The demo search query.

  • k (int) – How many hits to request.

Return type:

dict

Returns:

{"ingested": <summary dict>, "top_hit": <id or None>, "caption": <str>}.

foley.derive_license_flags(license_id, *, overrides=None)[source]#

Look up the flag set for a license_id (fail-closed fallback), then apply per-source overrides.

Parameters:
  • license_id (str) – The normalized license id (SPDX or foley-specific token).

  • overrides (Optional[dict]) – Optional per-source flag overrides — e.g. Freesound forces cache_bytes_ok=False on CC0. Keys must be LicenseFlags fields.

Return type:

LicenseFlags

Returns:

The resolved LicenseFlags (fallback = all-False UNKNOWN_LICENSE_FLAGS for unrecognized ids).

Raises:

ValueError – If overrides contains a key that is not a LicenseFlags field.

foley.detect_clipping(samples, *, full_scale=0.999, min_run=3)[source]#

Detect hard (flat-topped) clipping.

A frame is “hot” when any channel reaches |x| >= full_scale. Only maximal hot runs of length >= min_run count as clip events.

Parameters:
  • samples (ndarray) – Waveform in [-1, 1].

  • full_scale (float) – Absolute level at/above which a sample is full-scale.

  • min_run (int) – Minimum consecutive full-scale frames to count as clipping.

Return type:

tuple[float, int]

Returns:

(clipped_ratio, max_run_length) — the fraction of frames inside counting runs, and the longest counting run ((0.0, 0) if none).

foley.duration_s(samples, sample_rate)[source]#

Clip duration in seconds: frames / sample_rate.

Return type:

float

foley.encode(samples, sample_rate, *, fmt='flac', subtype='PCM_24')[source]#

Encode samples fully in memory and return the container bytes.

This is the producer for the content-addressed byte store: default output is the FLAC archive form.

Parameters:
  • samples (ndarray) – The working array to encode.

  • sample_rate (int) – Sample rate in Hz.

  • fmt (str) – Container/codec name (case-insensitive).

  • subtype (str) – Sample subtype (e.g. PCM_24).

Return type:

bytes

Returns:

The encoded audio as bytes.

Lazy dependency: soundfile.

foley.ensure_channels(samples, *, channels)[source]#

Coerce samples to exactly channels channels.

Mappings: mono -> N by duplication; N -> mono by mean; N -> M (N != M, both > 1) by collapsing to mono then tiling up to M.

Parameters:
  • samples (ndarray) – Mono or multichannel working array.

  • channels (int) – Target channel count (must be >= 1).

Return type:

ndarray

Returns:

A (frames,) array when channels == 1, else a (frames, channels) array.

Raises:

ValueError – If channels < 1.

Lazy dependency: numpy (only for the up-mix / tile path).

foley.estimate(verb, **kwargs)[source]#

What a call would cost in USD before making it — None when it cannot be known (#57).

None is never “free”: it means the price is unknown (no pricing declared, a remote LLM endpoint, an unlisted model), and such a call needs the run’s approval (Budget(approve_unknown_cost=True)). Paid amounts are upper bounds.

Parameters:
  • verb (str) – 'generate' (backend=, plus the generation affordances — priced exactly as the call would be: same drops, same clamp); 'find' / 'score' (the run’s upper bound: every generation and LLM call it could make — what find checks against its budget before the first paid call; pass context= / segments= so the decomposer’s prompt is sized, and score sums one find per segment); 'search' / 'similar' / 'ingest' (local: 0.0); 'add_from' (source=).

  • **kwargs – The same keywords the verb takes.

Raises:
foley.estimate_snr(samples, sample_rate, *, quiet_percentile=10.0, frame_s=0.025, hop_s=0.01)[source]#

Estimate SNR in dB (advisory — a busy-street SFX legitimately scores low).

The noise floor is the mean short-time RMS of the quietest quiet_percentile percent of frames; the signal level is the whole-clip RMS. A near-noise-free clip (quiet frames -> ~0) yields a very high value; an exactly-zero floor returns inf and a silent clip returns -inf.

Parameters:
  • samples (ndarray) – Waveform in [-1, 1] (down-mixed to mono internally).

  • sample_rate (int) – Sample rate in Hz (sizes the frames).

  • quiet_percentile (float) – Percent of quietest frames forming the noise floor.

  • frame_s (float) – Short-time frame length in seconds.

  • hop_s (float) – Hop between frames in seconds.

Return type:

float

Returns:

SNR in dB.

foley.evaluate(*, golden=None, k=10)[source]#

Run the Tier-1 retrieval eval over the golden set (nDCG@10 / recall / mAP / MRR).

Scores every golden query through the real SoundLibrary.search() path against a deterministic, CLAP-free Ring-0 library — the same computation the PR gate asserts on. See foley.eval.

Parameters:
  • golden – Optional path to a golden-set JSON (default: the frozen seed).

  • k (int) – Retrieval cutoff and metric @k.

Returns:

A foley.eval.RetrievalReport.

foley.evaluate_fit(*, golden=None, sample=None, level=VerifyLevel.judge, fit_judge=None, embedder=None, seed=0, k=10, llm=None)[source]#

Run the Tier-2 fit eval over the golden set — “does the accepted clip fit?” (#10b).

The judge-based sibling of evaluate(): over a seeded stratified sample it runs the SELECT pipeline and audits each license-clean candidate with the authoritative fit-judge, returning a foley.eval.FitReport (fit-precision / recall / F1 + fit-score + auto-accept-rate + per-stratum breakdown). Works out of the box on the Ring-0 fixture with the deterministic fake judge — no network, key, or heavy deps. Nightly / pre-release and cost-gated: report-only — gating is the caller’s job via FitReport.gate(). It never touches the retrieval ranking (the Tier-1 nDCG gate).

Parameters:
  • golden – Optional golden-set JSON path (default: the frozen Ring-0 seed).

  • sample – Optional stratified sample cap (default: the whole set — the cost gate).

  • level – The verify rung the fit-judge audits at — 'listen' or 'judge' (default VerifyLevel.judge); 'clap' is rejected.

  • fit_judge – An injected authoritative judge (default: the llm provider’s judge — the hermetic StringOverlapJudge fake unless llm opts in; the audio-LM AudioLMJudge is injection-only).

  • embedder – The Ring-0 embedder (default: the CLAP-free HashingBowEmbedder).

  • seed (int) – The sampling RNG seed.

  • k (int) – Retrieval shortlist depth per event.

  • llm – The fit-judge’s provider when fit_judge is not given — 'anthropic' for the nightly arbiter (or $FOLEY_LLM); None keeps the deterministic fake (a local endpoint is never picked implicitly).

Returns:

A foley.eval.FitReport.

foley.fade(samples, sample_rate, *, fade_in_s=0.01, fade_out_s=0.01, kind='linear')[source]#

Apply in/out gain ramps to samples (a short declick by default).

Ramp lengths are clamped to at most len(samples) // 2 so the in- and out-ramps never overlap on tiny inputs.

Parameters:
  • samples (ndarray) – Working array (mono or multichannel).

  • sample_rate (int) – Sample rate in Hz (converts the fade durations to samples).

  • fade_in_s (float) – Fade-in duration in seconds.

  • fade_out_s (float) – Fade-out duration in seconds.

  • kind (str) – 'linear' or 'equal_power' ramp shape.

Return type:

ndarray

Returns:

A new array with the fade envelope applied (input is not mutated).

Lazy dependency: numpy.

foley.find(context, *, max_events=6, seconds=None, intended_use=None, backend='auto', verify='listen', stream=False, k=10, tau_retrieve=0.5, tau_clap=0.35, max_refine_loops=1, budget=None, library=None, decomposer=None, judge=None, refiner=None, llm=None)[source]#

The headline: a narrative context → verified, license-clean sound candidates.

decompose → (per event) refine/search → verify_match ladder → decide (with the fail-closed license gate FIRST) → place (report 05 §5). Works out of the box — foley.find("She pushed open the heavy oak door; rain hammered outside.") — with deterministic defaults; every model / threshold / seam is an optional keyword.

Parameters:
  • context (str) – The narrative passage.

  • max_events (int) – The sparse density cap on decomposed events.

  • seconds (Optional[float]) – Optional passage duration (density-window hint; forwarded).

  • intended_use (Optional[IntendedUse]) – The caller’s rights intent (default: foley.licensing.DEFAULT_INTENDED_USE — commercial publishing; NC / SA material is refused unless you pass a different intent).

  • backend (str) – Generation backend for the fallback ('auto' → foley.generate’s default).

  • verify (Union[str, VerifyLevel]) – The max verify rung — 'clap' | 'listen' | 'judge'.

  • stream (bool) – If True, return a generator yielding one Candidate per resolved event; else return the collected list.

  • k (int) – Retrieval shortlist depth per query.

  • tau_retrieve (float) – Confidence threshold to auto-accept a retrieved clip.

  • tau_clap (float) – The clap-rung gate threshold.

  • max_refine_loops (int) – Max refine→re-retrieve passes per event (also the default Budget).

  • budget (Optional[Budget]) – An explicit Budget (overrides max_refine_loops). It is also the run’s spend cap: Budget(max_usd=...) (default $1; $3 when llm='anthropic' or $FOLEY_LLM=anthropic, whose default run is bounded at about $2.52 — foley.cost.DEFAULT_MAX_USD_BY_LLM) bounds every paid call the run makes — generations and paid LLM rungs — cumulatively, and a call of unknown cost needs Budget(approve_unknown_cost=True) (#57).

  • library – Target SoundLibrary (default: the process-wide default).

  • refiner (decomposer / judge /) – Injected DI seams (Decomposer / Judge / Refiner); each defaults to the llm provider’s implementation.

  • llm (Optional[str]) – Which LLM the decompose / refine / judge rungs use when not injected — 'fake' | 'local' | 'anthropic'. None reads $FOLEY_LLM, then falls back to the free default (a configured local endpoint, else the deterministic fake). A key being present never opts in to paid calls.

Return type:

Union[list[Candidate], Iterator[Candidate]]

Returns:

list[Candidate] (stream=False) or an Iterator[Candidate] (stream=True) — one verified, license-clean candidate per resolved event.

foley.fuse_hits(vector_hits, keyword_hits, *, k, rrf_k=60)[source]#

RRF-fuse a vector ranker’s hits with a keyword ranker’s hits.

Parameters:
  • vector_hits (list[tuple[str, float]]) – [(id, cosine_similarity), ...] best-first (from knn()).

  • keyword_hits (list[tuple[str, float]]) – [(id, bm25_score), ...] best-first (from bm25()).

  • k (int) – Number of fused hits to return.

  • rrf_k (int) – The RRF damping constant.

Return type:

list[FusedHit]

Returns:

The top-k :class:`FusedHit`s, each carrying its raw component scores.

foley.generate(prompt, *, backend='stable_audio', library=None, store=True, adapter=None, watermark=None, on_flagged='refuse', watermarker=None, provenance_store=None, on_unsupported=None, reuse_cached=True, budget=None, **affordances)[source]#

Generate a sound effect for prompt and add it to the library (by-value).

Progressive disclosure: foley.generate("a single wooden door creak") works out of the box (the local Stable Audio Open backend, into the process-wide default library). The generated audio is stored by-value with a content-hash id, so it becomes a first-class, re-searchable library entry — every generation is a future free retrieval (the generation flywheel). It flows through the SAME ingest_one() pipeline as every other source, with operator consent for the generator license’s AI-training restriction (the record keeps ai_training_ok=False, so keep() still refuses it for training uses).

Parameters:
  • prompt (str) – The natural-language sound description.

  • backend (str) – A registered generate source — "stable_audio" (default, local; needs foley[stable-audio]) or "elevenlabs" (hosted; foley[elevenlabs] + $ELEVENLABS_API_KEY).

  • library – Target library (default: the process-wide default library).

  • store (bool) – If False, synthesize + enrich a preview without adding it.

  • adapter – Optional pre-built adapter (the DI seam; production omits it and the registry lazily builds one).

  • watermark – True require an AudioSeal watermark, False never, None (default, auto) watermark iff foley[provenance] is installed (#9b).

  • on_flagged (str) – 'refuse' (default, fail-closed) or 'warn' for a prompt that trips the trademarked-audio / recognizable-voice safety gate (#9b).

  • watermarker – An injected watermarker (the DI seam; tests pass a fake).

  • provenance_store – A MutableMapping for content-credential sidecars (default: foley.stores.make_provenance_store()).

  • on_unsupported – A parameter the backend cannot honour: None (default) raises for a meaning-carrying one (seed, negative_prompt) and drops the rest with a note; 'warn' drops everything with a note + warning; 'note' silently-but-recorded; 'raise' raises for any.

  • reuse_cached (bool) – Serve an identical paid request from the generations cache (default True): a paid sound is never paid for twice (#59).

  • budget – A Budget for this call (default: the active run’s, else a fresh one — a $1 cap; an unknown cost needs approval) (#57).

  • **affordances – Unified generation affordances (duration, prompt_influence, negative_prompt, steps, seed, loop, output_format — see GENERATION_AFFORDANCES).

Returns:

The stored Candidate (origin=generated) — its sound is the canonical, by-value SoundRecord (a content-hash id); its notes list every dropped, clamped or substituted parameter, and cost_estimate_usd / cost_actual_usd what it cost (None = unknown).

Raises:
  • SafetyRefusal – If the prompt trips a safety gate and on_flagged='refuse' (a GenerationError subclass — TrademarkRefusal / RecognizableVoiceRefusal).

  • GenerationError – If the backend yields no stored sound (QC-quarantined, rights-blocked, or a synthesis/ingest error). The message carries the root cause and the exception is chained to it; it also carries the full report and terminal status so callers can react distinctly.

  • SourceConfigurationError – If the backend is not configured (a missing key or plan) — the message names the env var and where to get the key.

  • UnsupportedParameter – For a meaning-carrying parameter the backend cannot honour (see on_unsupported).

  • BudgetExceeded / CostApprovalRequired – Before a paid call the budget refuses.

  • EgressBlocked – For an external backend under offline(), or ModelNotCached for a local one whose weights are not on this machine.

foley.has_nan_inf(samples)[source]#

Return True if any sample is NaN or Inf (corrupt-clip guard).

Return type:

bool

Embed query, run the vector + keyword rankers, and RRF-fuse them.

Parameters:
  • query (str) – The natural-language query.

  • embedder (Embedder) – Text<->audio embedder (its embed_text produces the query vector).

  • vindex (VectorIndex) – The vector index (CLAP KNN).

  • kindex (KeywordIndex) – The keyword index (BM25).

  • k (int) – Number of fused results to return.

  • candidate_k (int) – Shortlist depth pulled from each ranker before fusion.

  • rrf_k (int) – The RRF damping constant.

  • where (Optional[dict]) – Optional metadata push-down passed to both rankers.

Return type:

list[FusedHit]

Returns:

The top-k fused :class:`FusedHit`s.

foley.ingest(path, *, library=None, backend='local', qc=True, recursive=True, **kw)[source]#

Ingest a folder (or single file) of sounds into the default library.

probe -> QC -> tag -> zero-shot -> caption -> embed -> SoundRecord for each file, returning an IngestReport. See foley.index.ingest_folder() / ingest_one() for the per-file options (license, taggers, min_status, …).

Parameters:
  • path – A folder (walked) or a single audio file.

  • library – Target library (default: the process-wide default library).

  • backend (str) – "local" ingests filesystem audio; other backends (a source adapter pull) route through add_from (subtask #5) — kept in the signature for forward-compat.

  • qc (bool) – Run the Tier-0 QC gate (quarantines failing clips).

  • recursive (bool) – Recurse into sub-folders.

  • **kw – Forwarded to foley.index.ingest_one() — notably license: omit it and the files’ rights are unknown (indexed, but keep() refuses them for every use); pass license="user-owned" to assert you own them (#55).

foley.ingest_folder(path, *, library=None, recursive=True, exts=('.wav', '.flac', '.aiff', '.aif', '.ogg', '.mp3', '.opus', '.m4a'), on_error='collect', **ingest_one_kw)[source]#

Ingest every audio file under path and return an IngestReport.

Parameters:
  • path – A folder (walked) or a single audio file.

  • library – Target library (default: the process-wide default).

  • recursive (bool) – Recurse into sub-folders.

  • exts (tuple[str, ...]) – Audio extensions to ingest.

  • on_error (str) – 'collect' records per-file errors and continues; 'raise' re-raises the first error. Either way an EgressBlocked (offline, the embedder’s weights not cached) raises at once: every file would fail the same way.

  • **ingest_one_kw – Forwarded to ingest_one() (license, taggers, QC flags, …).

Return type:

IngestReport

Returns:

An IngestReport (with .summary() counts and per-file results).

foley.ingest_one(src, *, library=None, sound_id=None, source_uri=None, license=None, tagger=None, zeroshot_tagger=None, captioner=None, do_qc=True, min_status=QCStatus.warn, do_supervised=True, do_zeroshot=True, do_caption=True, thresholds=QCThresholds(clip_full_scale=0.999, clip_min_run=3, clip_reject_ratio=0.0001, clip_reject_run=10, true_peak_max_dbtp=-1.0, true_peak_oversample=4, dc_offset_fail=0.01, dc_offset_warn=0.001, silence_rms_dbfs=-60.0, snr_clean_db=20.0, snr_quiet_percentile=10.0, snr_frame_s=0.025, snr_hop_s=0.01, edge_rel_peak_dbfs=-40.0, edge_fade_s=0.01, lufs_gate_floor=-70.0, lufs_outlier_lu=6.0, duration_min_s=0.1, deliver_min_sample_rate=44100), store=True, allow_ai_training_forbidden=False, seed_tags=None, commercial=None)[source]#

Ingest one clip into library and return an IngestResult.

Pipeline: probe + decode-once -> content-address dedup -> QC gate -> embed (once) -> supervised + zero-shot tags -> caption -> resolve UCS -> assemble SoundRecord -> SoundLibrary.add().

Parameters:
  • src (Union[str, PathLike, bytes, BinaryIO]) – A path, bytes, or file-like audio source.

  • library – Target SoundLibrary (default: the process-wide default library).

  • sound_id (Optional[str]) – Optional canonical id override. Defaults to None → the content-hash of the decoded PCM (the local-ingest identity, used as the record id and dedup key). A live source adapter passes a short, case-stable, source-native id (e.g. 'freesound:12345') so dedup keys on the stable id rather than on re-fetched (lossy, byte-varying) preview bytes; when it does, the PCM hash is computed only for the (skipped) default and is not persisted. Separately, content_sha256 records the hash of the stored FLAC archive bytes (set by store_sound), which is a different byte source from this PCM hash.

  • source_uri (Optional[str]) – Optional by-reference fetchable URI override. Defaults to None → the resolved local path when src is path-like. A live adapter passes the stable source page URL (e.g. 'https://freesound.org/s/12345/') that foley.stores.store_sound() requires for a by-reference sound.

  • license (Optional[LicenseRecord]) – Rights record, or a license_id string the caller asserts ('user-owned'), or None (default): rights unknown, so the clip is indexed but foley.keep() refuses it (#55). See resolve_ingest_license().

  • tagger – Supervised Tagger (default: PANNs via default_tagger()).

  • zeroshot_tagger – Zero-shot tagger (default: CLAP via default_zeroshot_tagger()).

  • captioner – Optional Captioner (default: none — the caption stage is off unless one is injected).

  • do_qc (bool) – Run the Tier-0 QC gate.

  • min_status (QCStatus) – Admission floor — a QC status worse than this is quarantined (default warn: only fail clips are rejected).

  • do_caption (bool) – Toggle each enrichment stage.

  • thresholds (QCThresholds) – QC thresholds.

  • store (bool) – If False, assemble the record but do not add it to the library (probe/QC/enrich only).

  • seed_tags (Optional[list]) – Optional caller-supplied tags (e.g. a corpus’s folder-path taxonomy) unioned into the record’s tags alongside the supervised/zero-shot tags — so they feed the BM25 keyword index.

  • allow_ai_training_forbidden (bool) – The universal fail-closed rights gate. A sound whose license has ai_training_ok=False (e.g. Sonniss, BBC RemArc) is refused with status 'rights_blocked' before it is embedded or stored — CLAP-embedding-and-persisting is itself a form of AI training on the corpus. Pass True to record explicit operator consent and admit it anyway (see foley.bootstrap.bootstrap()’s accept_ai_restricted). Protects every ingest path, not just bootstrap.

  • commercial (Optional[bool]) – Whether the AI use is for a commercial purpose (decides a nc_open_source_only scope, #69). None (default) means DEFAULT_INTENDED_USE’s (commercial).

Return type:

IngestResult

Returns:

An IngestResult; its record is None when quarantined, a duplicate, or rights-blocked.

foley.install_agent_kit(dest='./.claude', *, overwrite=False)[source]#

Copy the shipped skill + slash command + subagent into dest (a .claude dir).

Installs:

  • dest/skills/foley-sound-design/ — the consumer skill (the sound-design playbook),

  • dest/commands/foley-score.md — the /foley-score slash command,

  • dest/agents/sound-designer.md — the sound-designer subagent.

Parameters:
  • dest – The target agent-config dir (default ./.claude in the cwd; pass ~/.claude to install globally for every project).

  • overwrite (bool) – Replace existing files/dirs (default: skip what already exists).

Return type:

list[str]

Returns:

The list of installed paths (as strings) — empty entries that already existed are skipped.

foley.is_offline()[source]#

Whether an offline runtime scope is currently active.

Return type:

bool

foley.is_silent(samples, *, rms_floor_dbfs=-60.0)[source]#

Return True when whole-clip RMS falls below rms_floor_dbfs.

A zero (exactly silent) clip has RMS 0 -> -inf dBFS -> True.

Return type:

bool

foley.keep(record, intended_use)[source]#

Fail-closed candidate license gate (report 07 §8.2).

Run BEFORE ranking/verification in the agent’s decide(). Unknown or unverified rights => reject. Any single unmet requirement => reject.

Parameters:
  • record (LicenseRecord) – The candidate’s rights record.

  • intended_use (IntendedUse) – The caller’s declared intent.

Return type:

bool

Returns:

True only if every requirement in intended_use is satisfied by record; False otherwise (including unverified rights).

foley.keep_sound(sound_record, intended_use)[source]#

Convenience: apply keep() to a SoundRecord’s nested license.

Parameters:
  • sound_record – A SoundRecord (its .license is the SSOT consulted).

  • intended_use (IntendedUse) – The caller’s declared intent.

Return type:

bool

Returns:

The result of keep(sound_record.license, intended_use).

foley.lancedb_available()[source]#

True if lancedb is importable (the foley[index] extra is present).

Return type:

bool

foley.license_id_from_cc_url(url)[source]#

Map a Creative-Commons license URL or label to (license_id, verified).

The single SSOT for turning an external source’s license string into a foley license_id (FSD50K, Clotho, the Freesound API, …). It never widens rights: the string is split into tokens, and

  • any NoDerivatives / ShareAlike sign (nd, sa, no derivatives, share alike, sharealike) → ('unknown', False): foley has no row expressing those restrictions, so the sound is refused everywhere;

  • the Public Domain Mark → ('PDM-1.0', False): a claim about the work, not a licence anyone granted, so never auto-verified (#56);

  • CC0 (publicdomain/zero, cc0, creative commons 0) → CC0-1.0;

  • Sampling+ → CC-Sampling+-1.0;

  • by / attribution with any mention of commercial use (nc, noncommercial, non commercial, no commercial use) → CC-BY-NC-<v>, else CC-BY-<v>. The version is kept (/by/3.0/ → CC-BY-3.0); a versionless label gets DEFAULT_CC_VERSION; a version foley has no row for (a 2.1/jp port) fails closed;

  • anything else — including a string with a token this parser does not know — → ('unknown', False).

Parameters:

url (Optional[str]) – A CC license URL, a CC label string, or None.

Return type:

tuple[str, bool]

Returns:

(license_id, rights_verified).

foley.license_meta(license_id)[source]#

Return the display LicenseMeta for license_id (fail-closed fallback).

Parameters:

license_id (str) – The normalized license id.

Return type:

LicenseMeta

Returns:

The mapped LicenseMeta, or UNKNOWN_LICENSE_META for an unrecognized / Proprietary-* id.

foley.list_sources(*, egress_allow=None)[source]#

Return the names of registered live sources (runs discovery first).

Parameters:

egress_allow (Optional[frozenset]) – If given, keep only sources whose declared config['data_egress'] is in this set (the local-first / offline filter — see foley.runtime.RuntimeConfig). A source that does not declare data_egress is excluded (fail-closed).

Return type:

list[str]

foley.load(src, *, target_sr=None, mono=False, dtype='float32')[source]#

Decode audio into a float working array.

src may be a filesystem path, raw encoded bytes (wrapped in a BytesIO so nothing touches disk), or any binary file-like object.

Parameters:
  • src (Union[str, PathLike, bytes, BinaryIO]) – Path, raw bytes, or file-like object to decode.

  • target_sr (Optional[int]) – If given, resample the decoded audio to this rate (via resample()); otherwise the native rate is returned.

  • mono (bool) – If True, down-mix multichannel audio to mono.

  • dtype (str) – NumPy dtype string for the returned array (default float32).

Return type:

tuple[ndarray, int]

Returns:

A (samples, sample_rate) tuple. samples has shape (frames,) (mono) or (frames, channels); sample_rate reflects any resample.

Lazy dependencies: soundfile (and soxr when target_sr differs).

foley.loudness_normalize(samples, sample_rate, *, target_lufs=-16.0, peak_ceiling_dbfs=-1.0, min_block_s=0.4)[source]#

Loudness-normalize to target_lufs, then keep it peak-safe.

Integrated loudness is measured (ITU-R BS.1770-4 / EBU R128), the signal is scaled to target_lufs, and finally attenuated so its sample peak sits at or below peak_ceiling_dbfs. Two inputs are returned unchanged (flag them, don’t amplify): near-silent input (measured loudness at or below LUFS_GATE_FLOOR), and a clip shorter than one BS.1770 gating block (min_block_s) — which pyloudnorm cannot measure and would otherwise raise ValueError on (routine for one-shots: clicks, blips, gunshots).

Parameters:
  • samples (ndarray) – Working array (mono or multichannel, time on axis 0 — the layout pyloudnorm expects).

  • sample_rate (int) – Sample rate in Hz.

  • target_lufs (float) – Desired integrated loudness (default = foley’s podcast target).

  • peak_ceiling_dbfs (float) – Sample-peak ceiling (dBFS) applied after loudness normalization. Note this is a sample-peak limit, not an inter-sample true-peak (dBTP) limit — see foley.qc.true_peak_dbtp() for the oversampled measurement.

  • min_block_s (float) – Minimum clip length (seconds) that can be loudness-measured; shorter clips are returned unchanged with measured = -inf.

Return type:

tuple[ndarray, float]

Returns:

(normalized, measured_input_lufs). When the input is near-silent or too short to measure, normalized is the unchanged input and measured_input_lufs is at or below LUFS_GATE_FLOOR (-inf for the too-short case).

Lazy dependencies: pyloudnorm (+ numpy).

foley.make_byte_store(rootdir=PosixPath('/home/runner/.local/share/foley/audio'))[source]#

Build the content-addressable blob store: Mapping[content_key -> bytes].

The local default is dol.Files (bytes values on disk). For cloud storage, build the equivalent store from any dol Mapping (e.g. an S3 store) and pass it directly to store_sound() instead of calling this factory — the store_sound gate treats sounds as an opaque MutableMapping.

Parameters:

rootdir (Union[str, PathLike[str]]) – Directory that holds the blobs (created if missing).

Return type:

MutableMapping[str, bytes]

Returns:

A MutableMapping[str, bytes] keyed by content_key().

foley.make_http_app(*, auth, path='/mcp', json_response=False, library=None, runtime=None, byte_store=None, include=None, name='foley')[source]#

Build a bearer-auth-gated ASGI app serving the foley MCP tools over streamable HTTP.

HTTP exposes the tool surface to the network, so auth is **required and fail-closed**: pass auth={'bearer_tokens': [...]} (a non-empty iterable of accepted tokens) — a request without a matching Authorization: Bearer <token> header gets a 401. The returned app (a Starlette/ASGI callable) mounts in any ASGI host (uvicorn, gunicorn, a parent FastAPI). py2mcp / fastmcp are imported lazily inside build_mcp_server(), so import foley stays dol-only.

Parameters:
  • auth (dict) – {'bearer_tokens': [...]} — required; empty/missing raises (fail-closed).

  • path (str) – The MCP HTTP mount path.

  • json_response (bool) – Return a single JSON response instead of an SSE stream (simple clients).

  • name (str) – As build_mcp_server().

Returns:

An ASGI application (the bearer-gated MCP HTTP app).

Raises:

ValueError – If auth carries no bearer tokens (no anonymous HTTP access).

foley.make_meta_store(rootdir=PosixPath('/home/runner/.local/share/foley/meta'))[source]#

Build the metadata store: Mapping[sound_id -> SoundRecord] (JSON files).

SoundRecord values are (de)serialized transparently via the SerializableMixin (to_dict / from_dict); each record is written as a percent-encoded {sound_id}.json file while the store’s keys stay the bare sound_id (invariant #3 — the id is escaped at this boundary so an externally-derived id can never escape rootdir or collide via //..).

Parameters:

rootdir (Union[str, PathLike[str]]) – Directory that holds the metadata JSON files (created if missing).

Return type:

MutableMapping[str, SoundRecord]

Returns:

A MutableMapping[str, SoundRecord] keyed by sound_id.

foley.make_run_store(rootdir=PosixPath('/home/runner/.local/share/foley/runs'))[source]#

Build the run-artifact store: Mapping[run_id -> RunManifest dict] (JSON files).

The by-value carrier for #11’s reproducible run-manifests (one per instrumented find() / generate() / … ). An exact sibling of make_provenance_store(): escapes the run_id to a safe {enc}.json filename (invariant #3) while exposing bare run_id keys; values are plain dicts ((de)serialized by dol.JsonFiles). Local by default; swap in any dol Mapping for the cloud.

Parameters:

rootdir (Union[str, PathLike[str]]) – Directory that holds the run JSON files (created if missing).

Return type:

MutableMapping[str, dict]

Returns:

A MutableMapping[str, dict] keyed by run_id.

foley.make_session_store(session_id='default', name='picks', *, rootdir=None)[source]#

Build a per-session JSON store: Mapping[key -> dict] under sessions/{id}/{name}/.

The by-value carrier for #12’s audition state — one store per namespace (candidates / picks / rejects). A sibling of make_run_store(): percent-encodes each key to a safe {enc}.json filename (invariant #3) while exposing bare keys; values are plain dicts. Local by default; swap in any dol Mapping for the cloud.

Parameters:
  • session_id (str) – The session namespace (default 'default').

  • name (str) – The store namespace within the session (candidates / picks / rejects).

  • rootdir (Union[str, PathLike[str], None]) – Root sessions directory (default: DEFAULT_SESSION_DIR).

Return type:

MutableMapping[str, dict]

Returns:

A MutableMapping[str, dict] keyed by the bare key.

foley.mcp_server(*, library=None, session='default', runtime=None, byte_store=None, include=None, name='foley', max_usd=None)#

Build the foley MCP server (lazy py2mcp); registers the JSON-safe tool surface.

Validates that every source declares a data_egress (fail-closed), binds the injectable library / runtime / byte-store, and hands the resolved tool functions to py2mcp.mk_mcp_server. Never starts a server or touches the network.

Parameters:
Returns:

A fastmcp.FastMCP server.

foley.measure_lufs(samples, sample_rate, *, gate_floor_lufs=-70.0, min_block_s=0.4)[source]#

Integrated loudness (LUFS, ITU-R BS.1770-4) via pyloudnorm (lazy).

Returns None when pyloudnorm is unavailable, the clip is shorter than one gating block (min_block_s), the samples are non-finite, or the measured loudness is at/below the gate floor (near-silent / unstable — do not amplify, just flag).

Return type:

Optional[float]

foley.needs_edge_fade(samples, *, rel_peak_dbfs=-40.0)[source]#

Return True when the first or last sample sits above rel_peak_dbfs relative to the clip peak — i.e. a nonzero boundary that clicks under narration and needs a short fade.

Return type:

bool

foley.offline(config=None)[source]#

Alias of offline_scope() — with foley.offline(): ... for local-first runs.

foley.parse_ucs_filename(filename, *, table=None)[source]#

Parse a UCS-conformant filename to (ucs_category, ucs_subcategory).

Fail-quiet: returns (None, None) when the name is not UCS-conformant or its CatID token is unknown (so a wrong subcategory is never emitted).

Parameters:
  • filename – A path or filename (only the basename’s token 0 is used).

  • table (Optional[UcsTable]) – The UCS table to resolve against (defaults to default_ucs_table()).

Return type:

tuple[Optional[str], Optional[str]]

foley.plan(candidates, *, transcript=None)[source]#

Fold verified candidates into the SPARSE SoundDesignTimeline (the SELECT→WEAVE bridge).

One TimelineItem per candidate (onset·gain·layer·loop only, from its SoundEvent), joined to the run-artifact via run_manifest_ref — the reserved #8 plan_ref slot is filled when called inside an active foley.obs run scope (None-safe otherwise).

Parameters:
  • candidates (list[Candidate]) – The candidates returned by find().

  • transcript (Optional[str]) – Optional narration transcript (WEAVE resolves the reference).

Return type:

SoundDesignTimeline

foley.preview(candidate_or_id, *, seconds=6, library=None, byte_store=None, session=None)[source]#

Produce a short audition of a sound; set its preview_uri to a store key.

Writes the first seconds of the clip (FLAC) into byte_store under its content key and points Candidate.preview_uri at that key — referencing the audio, never returning bytes. Fail-safe: if the audio codec extra (foley[audio]) or the clip is unavailable, preview_uri is None (the sound id + duration still let a client fetch it).

Parameters:
  • candidate_or_id – A Candidate or a sound id.

  • seconds (int) – Audition length.

  • library – The foley.index.SoundLibrary (default: the shared one).

  • byte_store – A MutableMapping[str, bytes] to hold the preview (default: none — then preview_uri stays None).

  • session (Optional[SessionStore]) – Optional session (unused here; accepted for a uniform signature).

Return type:

Candidate

Returns:

The candidate with preview_uri set (or None on graceful degradation).

foley.reciprocal_rank_fusion(ranked_id_lists, *, k=60)[source]#

Fuse several ranked id lists into one, by reciprocal rank.

Parameters:
  • ranked_id_lists (list[list[str]]) – Each element is a list of ids in descending-relevance order (best first). Lists may overlap and may differ in length.

  • k (int) – The RRF damping constant (default RRF_K = 60).

Return type:

list[tuple[str, float]]

Returns:

[(id, fused_score), ...] sorted by fused score descending, ties broken by id ascending (so the fusion is fully deterministic).

foley.refine(session=None, *, query=None, picked_ids=(), rejected_ids=(), hint=None, n=3, k=10, library=None, refiner=None)[source]#

Relevance-feedback refinement: expand for recall, boost picks, drop rejects, re-rank.

Distinct from foley.refine_query() (which only paraphrases a query): this reads the session’s picks/rejects (or the explicit picked_ids / rejected_ids), expands the query into paraphrases for recall, gathers neighbours of every pick, drops the rejects, and re-ranks by score.

Parameters:
  • session (Optional[SessionStore]) – The audition session (source of picks/rejects when not passed explicitly).

  • query (Optional[str]) – The base text query to expand (optional).

  • rejected_ids (tuple[str, ...]) – Explicit feedback (override the session’s).

  • hint (Optional[str]) – A steer for the query expansion.

  • n (int) – Paraphrases to request.

  • k (int) – Result depth.

  • library – The foley.index.SoundLibrary (default: the shared one).

  • refiner – The query-expansion seam (default: the deterministic fake).

Return type:

RefineResult

Returns:

A RefineResult.

foley.refine_query(query, *, n=3, hint=None, refiner=None, _span=None)[source]#

Expand query into up to n paraphrases for multi-query retrieval.

Parameters:
  • query (str) – The event query to expand.

  • n (int) – Number of paraphrases.

  • hint (Optional[str]) – Optional verify-failure reason to steer re-retrieval.

  • refiner (Optional[Refiner]) – An injected Refiner (the DI seam); defaults to _default_refiner().

  • _span – Internal — the obs span handle for GenAI recording.

Return type:

list[str]

foley.register_source(name, config, adapter=None)[source]#

Register a live source directly (out-of-tree plugin or a test double).

Overwrites any existing entry for name — the seam a test uses to inject a fake-transport-backed adapter. If adapter is None it is lazily built from config on first get_source() (the source must then be an importable foley.sources.<name> package).

Parameters:
  • name (str) – The source name (the add_from() / get_source() key).

  • config (dict) – The SOURCE_CONFIG declaration.

  • adapter – An optional pre-instantiated adapter (bypasses lazy loading).

Return type:

None

foley.resample(samples, sample_rate, *, target_sr=48000, quality='HQ')[source]#

Resample samples to target_sr (a no-op when already there).

Parameters:
  • samples (ndarray) – Working array (mono (frames,) or (frames, channels)).

  • sample_rate (int) – The array’s current rate in Hz.

  • target_sr (int) – Desired output rate in Hz (default = the working rate).

  • quality (str) – soxr quality preset (QQ/LQ/MQ/HQ/VHQ).

Return type:

ndarray

Returns:

The resampled array (the input unchanged when sample_rate == target_sr). dtype is preserved by soxr.

Lazy dependency: soxr.

foley.resolve_catid(*, tags=(), caption=None, audioset_labels=(), filename=None, table=None, audioset_map=None)[source]#

Resolve inputs to a best UCS CatID by the staged precedence.

Parameters:
Return type:

CatIdResolution

Returns:

A CatIdResolution (falsy when nothing resolved).

foley.resolve_master(master)[source]#

Resolve a master spec (profile name, explicit profile, or None) to a MasterProfile.

Parameters:

master (Union[str, MasterProfile, None]) – A MASTER_PROFILES key (e.g. 'podcast'), an explicit MasterProfile, or None (-> the podcast default).

Return type:

MasterProfile

Returns:

The resolved MasterProfile.

Raises:

ValueError – If master is an unknown profile name.

foley.restamp_rights(library=None, *, license='unknown', ids=None, where=None, select='legacy-user-owned', apply=False)[source]#

Re-stamp the licence of stored sounds; a dry run unless apply=True.

The migration for libraries built before #55 / #56. A re-ingest does not do it (content-addressed dedup skips files already stored), so this rewrites the stored records in place:

  • foley restamp-rights --apply — every sound an older foley stamped user-owned without being told becomes unknown (fail closed);

  • foley restamp-rights --license user-owned --apply — the same sounds, but you assert that you own them (now recorded with a verified_at timestamp);

  • foley restamp-rights --select legacy-elevenlabs --license elevenlabs-paid-plan --apply — ElevenLabs generations made before the plan was explicit;

  • foley restamp-rights --select has-license-url --license from-url --apply — every record that kept its source’s licence string (Freesound pulls) is re-derived by today’s mapper (CC versions, PDM, NC spellings);

  • corpus clips (Clotho, FSD50K) are repaired by re-running foley bootstrap, which re-stamps already-stored clips from the corpus metadata.

Parameters:
  • library – The SoundLibrary (default: the default one).

  • license (str) – The license_id to stamp (a LICENSE_FLAGS row; default 'unknown'), or FROM_LICENSE_URL to re-derive each record from its own license_url.

  • ids (Optional[list[str]]) – Re-stamp exactly these sound ids (overrides where / select).

  • where – A predicate LicenseRecord -> bool choosing the records (overrides select).

  • select (str) – A LEGACY_SELECTORS name (default 'legacy-user-owned').

  • apply (bool) – Write the change. False (default) only reports what would change.

Return type:

list[str]

Returns:

The ids that were (or, in a dry run, would be) re-stamped.

Raises:

ValueError – If license or select is unknown.

foley.run_qc(samples, sample_rate, *, thresholds=QCThresholds(clip_full_scale=0.999, clip_min_run=3, clip_reject_ratio=0.0001, clip_reject_run=10, true_peak_max_dbtp=-1.0, true_peak_oversample=4, dc_offset_fail=0.01, dc_offset_warn=0.001, silence_rms_dbfs=-60.0, snr_clean_db=20.0, snr_quiet_percentile=10.0, snr_frame_s=0.025, snr_hop_s=0.01, edge_rel_peak_dbfs=-40.0, edge_fade_s=0.01, lufs_gate_floor=-70.0, lufs_outlier_lu=6.0, duration_min_s=0.1, deliver_min_sample_rate=44100))[source]#

Run every Tier-0 check and fold the results into a QCReport.

Status rules (evaluated in order):

FAIL if has_nan_inf OR is_silent OR clipped_max_run >= clip_reject_run OR clipped_ratio > clip_reject_ratio OR duration_s < duration_min_s. WARN if dc_offset > dc_offset_fail OR needs_edge_fade OR (snr_db is a finite value < snr_clean_db) OR (true_peak_dbtp is a finite value > true_peak_max_dbtp). Otherwise PASS.

Each firing condition appends a human-readable string to notes. Two thresholds are intentionally NOT evaluated on a single source clip here because they belong to later stages: the library-median loudness-outlier check (+/- lufs_outlier_lu, a library-level concern) and the delivery sample-rate target (deliver_min_sample_rate, enforced at the weave/master stage).

Parameters:
  • samples (ndarray) – Waveform in [-1, 1] (mono or (frames, channels)).

  • sample_rate (int) – Sample rate in Hz.

  • thresholds (QCThresholds) – Overridable QC thresholds (defaults to shipped values).

Return type:

QCReport

Returns:

A populated QCReport.

foley.save(samples, sample_rate, dst, *, fmt='flac', subtype='PCM_24')[source]#

Write samples to dst as fmt/subtype (default = FLAC archive).

Parameters:
  • samples (ndarray) – The working array to write (shape (frames,) or (frames, channels)).

  • sample_rate (int) – Sample rate in Hz.

  • dst (Union[str, PathLike, BinaryIO]) – Destination path or writable binary file-like object.

  • fmt (str) – Container/codec name (case-insensitive; passed to libsndfile).

  • subtype (str) – Sample subtype (e.g. PCM_24, PCM_16, FLOAT).

Return type:

None

Lazy dependency: soundfile.

foley.score(segments, *, audio=None, transcript=None, library=None, intended_use=None, commercial_ok=None, max_events=6, verify='listen', master='podcast', weave=None, llm=None, budget=None, **weave_kwargs)[source]#

Choose sounds for narration text and (optionally) weave them into the narration audio.

Progressive disclosure — the AI-first headline:

foley.score("She pushed open the heavy oak door; rain hammered outside.")  # plan only
foley.score(segments, audio="narration.wav")  # + mastered mix, captions, credits

For each segment it runs the SELECT loop (decompose → search → verify → decide) with the fail-closed license gate and tasteful restraint, folds the chosen sounds into ONE editable SoundDesignTimeline, and — when audio is given (or weave=True) — aligns + weaves into a mastered mix. Returns a ScoreResult.

Parameters:
  • segments – The narration text — a single string, or a list of segment strings.

  • audio – The narration voice audio (path / bytes / ndarray / a library ref). When given, the result is woven into a mastered mix (set weave=False to skip).

  • transcript (Optional[str]) – The full narration transcript for alignment (default: the segments joined).

  • library – The foley.index.SoundLibrary (default: the process-wide default).

  • intended_use – The rights intent (default: foley.licensing.DEFAULT_INTENDED_USE — commercial publishing, the same default as foley.find() and every MCP tool).

  • commercial_ok (Optional[bool]) – Shorthand override of the default intent’s commercial; False explicitly admits non-commercial material.

  • max_events (int) – The sparse density cap per segment (restraint).

  • verify (str) – The max verify rung — 'clap' | 'listen' | 'judge'.

  • master (str) – The delivery MASTER_PROFILES target ('podcast' default).

  • weave (Optional[bool]) – Force weaving on/off; default auto (True iff audio is given).

  • llm (Optional[str]) – Which LLM the SELECT rungs use ('fake' | 'local' | 'anthropic'; None reads $FOLEY_LLM, else the free default) — see foley.find().

  • budget – The run’s Budget — one spend cap (max_usd) across every segment’s paid calls (#57). Default: $1, or $3 when llm (or $FOLEY_LLM) is 'anthropic' (foley.cost.default_max_usd(), #88).

  • **weave_kwargs – Forwarded to foley.weave() (e.g. sign_cert, watermark).

Return type:

ScoreResult

Returns:

A ScoreResult (timeline + events rationale; weave when woven).

foley.search(query, *, k=10, filters=None, commercial_ok=None, ucs_category=None, min_snr=None, duration_range=None, rerank=False)[source]#

Hybrid (CLAP vector ⊕ BM25) search of the default library.

Convenience wrapper over foley.library.search(...) — see foley.index.SoundLibrary.search(). Constructs the process-wide default library (local stores + CLAP + best available index) on first use.

commercial_ok defaults to the one rights intent every verb shares (foley.licensing.DEFAULT_INTENDED_USE: commercial), so only commercially usable sounds come back; pass commercial_ok=False to include the rest.

foley.serve_http(*, host='127.0.0.1', port=8000, auth, path='/mcp', **kwargs)[source]#

Build and serve the foley MCP tools over authenticated streamable HTTP (blocks).

Wraps make_http_app() and runs it with uvicorn. auth is required (fail-closed).

Return type:

None

foley.similar(sound_id, *, k=10, commercial_ok=None)[source]#

Find sounds similar to a stored sound (audio<->audio) in the default library.

See foley.index.SoundLibrary.similar(). Like search(), only sounds cleared for the default (commercial) intent are returned unless commercial_ok=False.

foley.similar_to(clip_or_candidate, *, k=10, library=None)[source]#

“More like this” — neighbours of a sound id / candidate, or of a raw clip.

A str id or a Candidate uses by-id neighbours (SoundLibrary.similar, self excluded); a raw working-array / bytes clip uses audio-to-audio search (SoundLibrary.search_clip).

Parameters:
Return type:

list[Candidate]

Returns:

A list of Candidate.

foley.sqlite_vec_loadable()[source]#

True if sqlite_vec is installed AND this interpreter can load it.

The macOS system / pyenv CPython builds frequently ship a sqlite3 without loadable-extension support (no enable_load_extension); on those, sqlite-vec cannot be used even when ``pip install``ed. This probes both.

Return type:

bool

foley.store_sound(record, data=None, *, sounds, meta, cache_bytes_ok=None)[source]#

Persist a sound, choosing by-value vs by-reference from cache_bytes_ok.

The choice is driven by the sound’s own license (invariant #1): unless cache_bytes_ok is passed explicitly, it is read from record.license.cache_bytes_ok. A sound whose bytes may NOT be cached (e.g. Freesound CC0, whose TOS forbids caching even though the file is legally redistributable — invariant #2) is stored by reference: no bytes are written, only its fetchable uri plus provenance.

Parameters:
  • record (SoundRecord) – The SoundRecord to persist; its nested license is the SSOT for the storage mode. Mutated in place with the resolved storage_mode / uri / content_sha256 and written into meta.

  • data (Optional[bytes]) – The canonical archive bytes (FLAC). Required for by-value storage; for by-reference it is optional — if given, its hash is recorded in content_sha256 for provenance but the bytes are NOT stored.

  • sounds (MutableMapping[str, bytes]) – The content-addressed byte store (see make_byte_store()).

  • meta (MutableMapping[str, SoundRecord]) – The metadata store (see make_meta_store()).

  • cache_bytes_ok (Optional[bool]) – Optional override. None (the default) means “use record.license.cache_bytes_ok”.

Return type:

SoundRecord

Returns:

The same (mutated) record, after it has been written into meta.

Raises:

ValueError – If record.id is empty/non-str (checked first, so a bad id never leaves an orphan blob), or if the sound resolves to by-reference storage but record.uri is empty (a by-reference sound must name a fetchable source URL).

Note

The blob is written BEFORE the record so a crash can never leave a metadata reference dangling against a missing blob.

foley.to_mono(samples)[source]#

Down-mix to mono by averaging channels; 1-D input passes through.

Parameters:

samples (ndarray) – Mono (frames,) or multichannel (frames, channels) array.

Return type:

ndarray

Returns:

A 1-D mono array (dtype preserved).

foley.to_working(samples, sample_rate, *, mono=True, target_sr=48000, dtype='float32')[source]#

Produce the canonical CLAP/QC working array from an arbitrary clip.

Down-mixes (when mono), resamples to target_sr, and casts to dtype — the float32 @ 48 kHz mono array every embedder/tagger/QC check consumes.

Parameters:
  • samples (ndarray) – Decoded working array (mono or multichannel).

  • sample_rate (int) – The array’s current rate in Hz.

  • mono (bool) – If True, down-mix to mono.

  • target_sr (int) – Working sample rate in Hz.

  • dtype (str) – Output NumPy dtype string.

Return type:

ndarray

Returns:

The canonical working array.

Lazy dependency: soxr (only when sample_rate != target_sr).

foley.trim_silence(samples, sample_rate, *, top_db=30.0)[source]#

Strip leading/trailing silence, returning the clip and its kept span.

Silence detection runs on a transient mono down-mix (so librosa’s time-last convention never clashes with foley’s time-first (frames, channels) layout); the returned sample indices then slice the original array along axis 0, preserving its channel layout.

Parameters:
  • samples (ndarray) – Working array (mono or multichannel).

  • sample_rate (int) – Sample rate in Hz (kept in the signature for API symmetry; trimming is index-based).

  • top_db (float) – A frame is silent when it sits at least this many dB below the reference (peak) level.

Return type:

tuple[ndarray, tuple[int, int]]

Returns:

(trimmed, (start_sample, end_sample)). On all-silent (or otherwise degenerate) input the original array is returned unchanged with a full-length span (0, len(samples)).

Lazy dependency: librosa.

foley.true_peak_dbtp(samples, sample_rate, *, oversample=4)[source]#

Inter-sample true-peak level in dBTP.

Each channel is band-limited-upsampled oversample``x (numpy FFT), the peak magnitude is taken across all channels, and converted to dBTP. Returns ``-inf for a fully silent clip. sample_rate is accepted for interface symmetry (FFT interpolation is rate-independent).

Return type:

float

foley.using_source_accounts(**accounts)[source]#

Bind per-request source accounts (a key, and what goes with it) for a with block.

The key is bound through ocracy.kit.using_credentials, so resolve_source_key() uses it; the rest of the account (ElevenLabs: its plan, which decides the licence) is bound with that key, and only applies while that same key is the one bound (#98):

with foley.using_source_accounts(
    elevenlabs={"key": user_key, "plan": "elevenlabs-free-plan"},
    freesound=user_freesound_key,  # a string is a key alone
):
    foley.generate("a door creaks", backend="elevenlabs")

A source given None (or an empty key) binds nothing, so an optional request header can be passed straight through.

The binding lives in a ContextVar: it covers what runs inside the block, in this context. Consume a lazy result (find(stream=True)) inside the block, and run work handed to a thread pool under contextvars.copy_context(); outside the block the operator’s key and plan apply again.

Raises:

ValueError – For a field an account cannot carry.

Pure audio<->audio (or clip->library) vector search — no keyword leg.

Used by SoundLibrary.similar and by searching with a reference clip. Hits keep their cosine similarity in clap_score and preserve the index’s own descending-similarity order (rrf_score is left None — there is no fusion).

Parameters:
  • qvec (ndarray) – An already-L2-normalized (dim,) query vector.

  • vindex (VectorIndex) – The vector index.

  • k (int) – Number of neighbours to return.

  • where (Optional[dict]) – Optional metadata push-down.

Return type:

list[FusedHit]

Returns:

Up to k :class:`FusedHit`s in descending-similarity order.

foley.verify_and_setup(*, names=None)[source]#

Return a per-requirement status + guidance report (never runs an installer).

Return type:

dict[str, dict]

Returns:

{name: {'available', 'purpose', 'install', 'url', 'probe'}}.

foley.verify_match(event, candidate, *, level=VerifyLevel.clap, judge=None, tau_clap=0.35, _span=None)[source]#

Verify candidate against event up to rung level (AND-confirming ladder).

Runs the clap gate always; if level is higher and the clap gate passed, escalates to the injected/​default judge for that rung and returns its verdict (Verdict.level == the producing rung).

Parameters:
  • event (SoundEvent) – The wanted SoundEvent.

  • candidate (Candidate) – A license-clean Candidate — this MUST run after the gate_candidates() gate (asserted).

  • level (str | VerifyLevel) – The max rung to climb (clap | listen | judge).

  • judge (Optional[Judge]) – An injected Judge for the higher rungs (the DI seam; defaults per _default_judge()).

  • tau_clap (float) – The clap-gate threshold.

  • _span – Internal — the obs span handle for GenAI recording on the LLM rung.

Raises:

AssertionError – If candidate.license_ok is not True (verify-before-gate is a bug — the license gate is the fail-closed first pass).

Return type:

Verdict

Modules

audio

Audio I/O and DSP primitives for foley.

index

foley INDEX stage — make every sound findable by keyword and meaning.

obs

Observability & the reproducible run-artifact for foley (#11).

agent

The SELECT stage — the search-agent that finds the right sound for a context (#7).

weave

eval

Tier-1 retrieval evaluation — metrics, a frozen golden set, and the nDCG gate.

provenance

Provenance layer for foley — attribution/credits (and, later, disclosure).

agent_kit

Install foley's shipped agent kit — the consumer skill + Claude slash command + subagent.

base

Canonical data models for foley — the single source of truth (SSOT) types.

cli

The foley command-line interface (stdlib argparse, zero new deps).

cost

Cost: what a call will cost before it is made, and a cumulative cap on a run (#57).

licensing

License policy for foley: the license_id -> flag-set SSOT, flag derivation (with per-source overrides), and the fail-closed candidate keep() gate.

qc

Tier-0 deterministic audio QC for foley (research report 08 §3).

requirements

Onboarding — check what foley needs, tell the user how to get it (accompy-style, #12).

runtime

Runtime posture — local-first / offline mode as one verifiable contract (#12, report 12).

sources

Bulk-corpus source adapters for foley's SOURCE stage.

stores

dol-backed storage for foley: content-addressed bytes + a metadata store.