Beat Foley
Extract dominant cycles from sound foley via qft so animators see rhythm and repetition that the ear or eye misses.
QFTยท periodicity / rhythm
Section ยท Quantum
full primer โThe hook.
A quantum frequency analyzer scans sound foley and surfaces the dominant cycles โ rhythms and repetitions filmmakers would otherwise miss โ as simple bars.
Why this primitiveQFT is the right primitive here because sound foley reduces to a periodicity / rhythm problem; the kernel returns a result you can drop straight into the UI.
Kernel
a Quantum Fourier Transform kernel that returns dominant frequency bins from a sampled signal
Drives the UI as
a spectral bars view that highlights dominant cycles
Appendix A
The mega-prompt.
This prompt is engineered to ship in a single Lovable build. Real Quantinuum Guppy/Selene circuit runs in the Linux sandbox at build time and the results are baked in as JSON. read the build strategy โ
~13.8 KB281 lines1 msg ยท ~5 credits
Build "Beat Foley"โa one-page quantum web app for filmmakers to extract dominant cycles from sound foley via QFT.
**BUDGET WARNING:** Lovable FREE tier (~5 credits). Ship in ONE message. No scope creep.
**BUILD ORDER (follow exactly):**
1. Create quantum/kernel.py: a @guppy QFT kernel that takes 64-sample signal, returns top-3 frequency bins + amplitudes (โค8 qubits).
2. Create quantum/run.py: import kernel, compile with guppylang, run 24 shots on Selene emulator (24 signals ร 64 samples), write src/data/quantum-results.json with shape [{input: [64 floats], output: {freqs: [f1, f2, f3], amps: [a1, a2, a3]}}].
3. Execute quantum/run.py in sandbox (pip install guppylang selene-sim first). Commit real JSON.
4. Frontend: TanStack Start, import JSON statically. Single route /.
5. UI: dark workspace. Left = Card with Slider to pick signal 0โ23. Center = line chart (Recharts) of input signal. Right = Bar chart of top-3 frequency bins (dominant cycles). Bottom = Card "About the quantum" with Tabs: "Results" (shows active record's freqs/amps) | "Guppy source" (inline code block of quantum/kernel.py).
6. Components: Card, Slider, Tabs, Badge (label frequencies), BarChart, LineChart. Tailwind dark mode. One accent color (electric blue or cyan for rhythm/motion).
7. Micro-interaction: Slider onChange highlights the active bar in the spectrum chart.
**Key constraint:** Every number on screen = real Selene output. No fake data. Judges see quantum/kernel.py source inline to verify QFT is genuine.
**Deliverable:** Single deploy. src/data/quantum-results.json committed with real Selene shots. App reads it at runtime (no Python at runtime).
--- QUANTINUUM APPENDIX (self-contained reference โ paste verbatim) ---
[1] INSTALL (Lovable Linux sandbox):
python -m pip install --no-cache-dir guppylang selene-sim
[2] IMPORTS (use exactly these โ no other quantum libs):
# quantum/kernel.py and quantum/run.py top
from guppylang import guppy
from guppylang.std.builtins import result
from guppylang.std.quantum import qubit, h, cx, rx, ry, rz, measure, discard, t as tgate, tdg
from guppylang.std.angles import angle, pi
from selene_sim import build, Quest
import math, json, sys, tempfile, importlib.util, uuid
from pathlib import Path
[3] HARD RULES (violating any breaks the build):
- @guppy reads source via inspect.getsource โ kernels MUST live in a real .py file on disk. No exec(), no REPL strings, no inline templates.
- Allowed gate set ONLY: h, rx, ry, rz, cx, tgate, tdg. There is NO native ccx/toffoli, cswap, cphase, or crz โ decompose using the snippets in [7].
- Qubit ownership: a qubit passed to a function is moved. You MUST measure() or discard() every qubit exactly once; never reuse after measure.
- Angle hygiene before baking a float into generated source:
theta = ((theta + math.pi) % (2.0 * math.pi)) - math.pi
and write it with repr: f"... {theta!r} ..." (str(float) can truncate).
[4] SELENE SHOT LOOP (canonical):
compiled = my_kernel.compile()
runner = build(compiled)
shots = []
for shot in runner.run_shots(Quest(), n_qubits=N, n_shots=S):
shots.append({str(lbl): int(v) for lbl, v in shot})
# N = MAX number of qubits simultaneously LIVE in the kernel.
# measure(q) releases the slot, so one ancilla reused across k windows still counts as 1.
[5] DRIVER PATTERN โ sweep a kernel over many inputs (closures do NOT work):
ROOT = Path(__file__).resolve().parent.parent
if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT))
def run_one(params: dict, shots: int = 256):
# Bake params as literals into a fresh .py file that imports your kernel helpers.
src = (
"from quantum.kernel import guppy, my_helper\n"
"@guppy\n"
"def program() -> None:\n"
f" my_helper({params['a']!r}, {params['b']!r})\n"
)
tmp = Path(tempfile.gettempdir()) / "qprogs"; tmp.mkdir(exist_ok=True)
name = f"prog_{uuid.uuid4().hex[:8]}"
path = tmp / f"{name}.py"; path.write_text(src)
spec = importlib.util.spec_from_file_location(name, path)
mod = importlib.util.module_from_spec(spec)
sys.modules[name] = mod # register BEFORE exec_module
spec.loader.exec_module(mod)
runner = build(mod.program.compile())
out = []
for shot in runner.run_shots(Quest(), n_qubits=5, n_shots=shots):
out.append({str(l): int(v) for l, v in shot})
return out
[6] PER-QUBIT INTEGER DECODE (host-side):
# kernel emits: for j in range(n): result(f"x{j}", measure(q[j]))
def decode(rec, n):
x = 0
for j in range(n): x |= (rec.get(f"x{j}", 0) & 1) << j
return x
[7] DECOMPOSITION LIBRARY (copy verbatim into quantum/kernel.py):
# ---- Toffoli (CCX) from H, CX, T, Tdg โ 6-T standard decomposition ----
@guppy
def toffoli(c1: qubit, c2: qubit, tgt: qubit) -> None:
h(tgt)
cx(c2, tgt); tdg(tgt)
cx(c1, tgt); tgate(tgt)
cx(c2, tgt); tdg(tgt)
cx(c1, tgt); tgate(c2); tgate(tgt)
h(tgt)
cx(c1, c2); tgate(c1); tdg(c2)
cx(c1, c2)
# ---- CSWAP (Fredkin) from CX + Toffoli ----
@guppy
def cswap(c: qubit, a: qubit, b: qubit) -> None:
cx(b, a)
toffoli(c, a, b)
cx(b, a)
# ---- Controlled phase exp(i*theta) on |11> from rz + cx ----
@guppy
def cphase(c: qubit, d: qubit, theta: float) -> None:
rz(d, angle(theta / 2.0))
cx(c, d)
rz(d, angle(-theta / 2.0))
cx(c, d)
# ---- Amplitude-encoded feature state (3 floats in [0,1] โ 2-qubit state) ----
@guppy
def prep_features(q0: qubit, q1: qubit, a: float, b: float, c: float) -> None:
ry(q0, angle(a))
ry(q1, angle(b))
cx(q0, q1)
rz(q1, angle(c))
# ---- SWAP test kernel; HOST inverts: F = clamp(2*P(anc=0) - 1, 0, 1) ----
@guppy
def swap_test(ai: float, bi: float, ci: float,
aj: float, bj: float, cj: float) -> None:
anc = qubit()
pi0 = qubit(); pi1 = qubit()
pj0 = qubit(); pj1 = qubit()
prep_features(pi0, pi1, ai, bi, ci)
prep_features(pj0, pj1, aj, bj, cj)
h(anc)
cswap(anc, pi0, pj0)
cswap(anc, pi1, pj1)
h(anc)
result("anc", measure(anc))
discard(pi0); discard(pi1); discard(pj0); discard(pj1)
# n_qubits = 5 for swap_test above.
[8] CLASSICAL CROSS-CHECK (NumPy reference โ commit alongside quantum result):
import numpy as np
I = np.eye(2); X = np.array([[0,1],[1,0]])
def RY(t): c,s = math.cos(t/2), math.sin(t/2); return np.array([[c,-s],[s,c]])
def RZ(t): return np.array([[np.exp(-1j*t/2),0],[0,np.exp(1j*t/2)]])
CX = np.array([[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]])
def prep_psi(a,b,c):
s = np.kron(RY(a)@np.array([1,0]), RY(b)@np.array([1,0]))
s = CX @ s
s = np.kron(I, RZ(c)) @ s
return s
def classical_fidelity(p, q):
return float(abs(np.vdot(prep_psi(*p), prep_psi(*q)))**2)
# Commit BOTH quantum and classical values per record:
# {"input": [...], "quantum": 0.873, "classical": 0.881, "shots": 256}
[9] FRONTEND HANDOFF (TanStack Start):
# quantum/run.py last step:
Path("src/data/quantum-results.json").write_text(json.dumps({
"records": records, # list of {input, quantum, classical, ...}
"circuit": {"n_qubits": 5, "shots": 256},
"kernel_source": Path("quantum/kernel.py").read_text(), # show on /code tab
}, indent=2))
# In React: import data from "@/data/quantum-results.json" (resolveJsonModule:true).
# Render data.records; render data.kernel_source in a <pre> inside the Quantum trace tab.
[10] BUILD ORDER (do not deviate โ keeps you under 5 credits):
1. Write quantum/kernel.py with the helpers from [7] you actually need.
2. Write quantum/run.py: driver pattern from [5], Selene loop from [4], cross-check from [8], JSON write from [9].
3. Execute once: python quantum/run.py (commit src/data/quantum-results.json).
4. Build the React route: static JSON import, shadcn Card + Tabs + Slider + Badge wiring.
5. Deploy. No Python at runtime; every on-screen number traces to committed Selene output.
[11] OUTPUT CONTRACT โ selene_run v1 (write this exact shape to src/data/quantum-results.json):
{
"schemaVersion": 1,
"experiment": "<idea-slug>",
"title": "<idea title>",
"description": "<one plain-English sentence โ what a non-quantum dev sees>",
"kernel": { "snippet": "<first 40 lines of quantum/kernel.py>", "qubits": <int>, "shotsPerRow": <int> },
"verdict": { "text": "<one-sentence headline of the result>", "good": true },
"metrics": [ { "name": "<label>", "value": <number>, "unit": "<%|bits|Hz|...>", "good": true } ],
"series": [ { "id": "<id>", "kind": "histogram"|"bar"|"line", "title": "...",
"xLabel": "...", "yLabel": "...", "yKeys": ["count"],
"points": [ { "label": "<x>", "values": { "count": <num> } } ] } ]
}
Reason: one schema = one renderer = a polished demo without bespoke React per idea.
All metric / series VALUES must come from real Selene shot data committed in step 3 โ no mocks.
[12] RENDERER โ create src/components/SeleneRunView.tsx with EXACTLY this body (no deps beyond react + tailwind):
import * as React from "react";
type Point = { label: string; values: Record<string, number> };
type Series = { id: string; kind: "histogram"|"bar"|"line"; title: string;
xLabel?: string; yLabel?: string; yKeys: string[]; points: Point[] };
type Metric = { name: string; value: number; unit?: string; good?: boolean };
export type SeleneRun = {
schemaVersion: 1; experiment: string; title: string; description: string;
kernel: { snippet: string; qubits: number; shotsPerRow: number };
verdict: { text: string; good: boolean };
metrics: Metric[]; series: Series[]; notes?: string;
};
const fmt = (n: number) => Math.abs(n) >= 100 ? n.toFixed(0) : Math.abs(n) >= 1 ? n.toFixed(2) : n.toFixed(3);
function Bars({ s }: { s: Series }) {
const max = Math.max(1, ...s.points.flatMap(p => s.yKeys.map(k => p.values[k] ?? 0)));
return (
<div className="space-y-1">
{s.points.map((p, i) => (
<div key={i} className="flex items-center gap-2 text-xs">
<div className="w-20 truncate text-muted-foreground">{p.label}</div>
<div className="flex-1 h-3 bg-muted rounded-sm overflow-hidden">
<div className="h-full bg-primary" style={{ width: `${(100*(p.values[s.yKeys[0]]??0))/max}%` }} />
</div>
<div className="w-12 text-right tabular-nums">{fmt(p.values[s.yKeys[0]]??0)}</div>
</div>
))}
</div>
);
}
function Line({ s }: { s: Series }) {
const W=320, H=120, P=20;
const ys = s.points.map(p => p.values[s.yKeys[0]] ?? 0);
const min = Math.min(...ys), max = Math.max(...ys), span = max - min || 1;
const pts = ys.map((y, i) => {
const x = P + (i*(W-2*P))/Math.max(1, ys.length-1);
const yy = H - P - ((y - min)/span)*(H - 2*P);
return `${x},${yy}`;
}).join(" ");
return (
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-32">
<polyline fill="none" stroke="currentColor" strokeWidth="2" points={pts} className="text-primary" />
</svg>
);
}
export function SeleneRunView({ run }: { run: SeleneRun }) {
return (
<div className="space-y-6">
<header>
<div className="text-xs uppercase tracking-wider text-muted-foreground">{run.experiment}</div>
<h2 className="text-2xl font-semibold">{run.title}</h2>
<p className="text-sm text-muted-foreground">{run.description}</p>
<div className={`mt-2 inline-block px-3 py-1 rounded-full text-xs ${run.verdict.good?"bg-emerald-500/15 text-emerald-400":"bg-amber-500/15 text-amber-400"}`}>
{run.verdict.text}
</div>
</header>
<section className="grid grid-cols-2 md:grid-cols-4 gap-3">
{run.metrics.map((m, i) => (
<div key={i} className="rounded-lg border border-border p-3">
<div className="text-[10px] uppercase tracking-wider text-muted-foreground">{m.name}</div>
<div className="text-xl font-semibold tabular-nums">{fmt(m.value)}<span className="text-xs text-muted-foreground ml-1">{m.unit}</span></div>
</div>
))}
</section>
<section className="space-y-6">
{run.series.map(s => (
<div key={s.id} className="rounded-lg border border-border p-4">
<div className="flex items-baseline justify-between mb-3">
<div className="text-sm font-medium">{s.title}</div>
<div className="text-[10px] text-muted-foreground">{s.xLabel} / {s.yLabel}</div>
</div>
{s.kind === "line" ? <Line s={s} /> : <Bars s={s} />}
</div>
))}
</section>
<footer className="text-[11px] text-muted-foreground">
kernel: {run.kernel.qubits} qubits ยท {run.kernel.shotsPerRow} shots/row
</footer>
</div>
);
}
Then in the route: import data from "@/data/quantum-results.json"; <SeleneRunView run={data as any} />.
Quantum trace tab: <pre>{data.kernel.snippet}</pre>.
[HOOK] QFT โ n-qubit Fourier transform.
for j in range(n):
h(q[j])
for k in range(j+1, n):
cphase(q[k], q[j], math.pi / (2 ** (k - j))) # cphase from [7]
# Then reverse the register with SWAPs (3 cx each) if you need standard bit order.
Read top frequencies host-side from the integer-decoded shot histogram (use decode() in [6]).
selene_run mapping:
metrics: [ {"name":"dominant bin","value":dom_bin,"unit":""},
{"name":"peak weight","value":100*peak_frac,"unit":"%"} ]
series: [ {"id":"spectrum","kind":"bar","title":"Frequency spectrum",
"xLabel":"bin","yLabel":"shots","yKeys":["count"],
"points":[{"label":str(b),"values":{"count":c}} for b,c in sorted(hist.items())]} ]Market sizing.
TAM
$20.0B
the animation industry (~$400B incl. film/TV) with >500K working animators.
SAM
$4.0B
the 20% of that market actively buying sound foley-adjacent software.
SOM
$40M
a realistic 1% capture of the serviceable slice in years 1โ3 via the hackathon launch and creator-led distribution.
Indicative figures for hackathon pitches โ refine with your own research before raising.
Adjacent entries.
storyboard generation
Pulse Generation
Extract dominant cycles from storyboard generation via qft so animators see rhythm and repetition that the ear or eye misses.
keyframe planningTempo Planning
Extract dominant cycles from keyframe planning via qft so animators see rhythm and repetition that the ear or eye misses.
character riggingCycle Rigging
Extract dominant cycles from character rigging via qft so animators see rhythm and repetition that the ear or eye misses.
scene pacingFrequency Pacing
Extract dominant cycles from scene pacing via qft so animators see rhythm and repetition that the ear or eye misses.