3e7e93ec-7a7c-4e6c-85be-68b1e1c2a9d2

This commit is contained in:
zai-bot 2026-11-07 03:45:21 +00:00
parent 60a7d40568
commit 5f46fb73fc
10 changed files with 137 additions and 1749 deletions

Binary file not shown.

View file

@ -8,7 +8,7 @@ datasource db {
} }
// A preset script file living somewhere in the folder tree. // A preset script file living somewhere in the folder tree.
// folderPath is the full path from the virtual root, e.g. "root/embedded/Synth-C". // folderPath is the full path from the virtual root, e.g. "root/embedded".
// Names are unique per folder (files in a directory). // Names are unique per folder (files in a directory).
// approx = true marks embedded presets that are generated stand-ins rather // approx = true marks embedded presets that are generated stand-ins rather
// than ports of the original's code (the original relies on effects the // than ports of the original's code (the original relies on effects the
@ -45,7 +45,7 @@ model Settings {
bandConfig String? bandConfig String?
beatConfig String? beatConfig String?
// Version stamp of the embedded preset library; when it changes, the // Version stamp of the embedded preset library; when it changes, the
// root/embedded subtree is re-seeded from BUILTIN + EMBEDDED_LIBRARY. // root/embedded subtree is re-seeded from EMBEDDED_LIBRARY.
embeddedSeed String? embeddedSeed String?
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
} }

View file

@ -1,23 +1,17 @@
import { NextRequest, NextResponse } from 'next/server' import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db' import { db } from '@/lib/db'
import { BUILTIN_PRESETS } from '@/lib/ava/presets'
import { EMBEDDED_LIBRARY, EMBEDDED_SEED_VERSION } from '@/lib/ava/embedded-presets' import { EMBEDDED_LIBRARY, EMBEDDED_SEED_VERSION } from '@/lib/ava/embedded-presets'
import { ensureFolderRows } from '@/lib/server/preset-tree' import { ensureFolderRows } from '@/lib/server/preset-tree'
import { EMBEDDED_ROOT, ROOT, USER_ROOT } from '@/lib/preset-paths' import { EMBEDDED_ROOT, ROOT, USER_ROOT } from '@/lib/preset-paths'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
/** The structural backbone of the tree, created on first run. */ /**
const BASE_FOLDERS = [ * The structural backbone of the tree, created on first run. The embedded
ROOT, * branch has no subfolders (v7.19.0 curation) — "Beat Matrix" lives directly
EMBEDDED_ROOT, * under root/embedded.
USER_ROOT, */
'root/embedded/Synth-C', const BASE_FOLDERS = [ROOT, EMBEDDED_ROOT, USER_ROOT]
'root/embedded/Amp Picks',
'root/embedded/Dynamics',
'root/embedded/Envision',
'root/embedded/Zai-Magic',
]
/** /**
* Version-gated seed + INTEGRITY SELF-HEAL for the embedded library. * Version-gated seed + INTEGRITY SELF-HEAL for the embedded library.
@ -26,18 +20,13 @@ const BASE_FOLDERS = [
* sees, so GET verifies it on every request: * sees, so GET verifies it on every request:
* 1. version gate — EMBEDDED_SEED_VERSION changes trigger a full re-seed * 1. version gate — EMBEDDED_SEED_VERSION changes trigger a full re-seed
* (library was regenerated); * (library was regenerated);
* 2. fingerprint — the embedded subtree must exactly match * 2. fingerprint — the embedded subtree must exactly match EMBEDDED
* BUILTIN + EMBEDDED (folder + name). Any drift — a preset renamed or * (folder + name). Any drift — a preset renamed or deleted through
* deleted through pre-hardening endpoints, a row injected straight into * pre-hardening endpoints, a row injected straight into the DB —
* the DB — re-seeds the subtree automatically. * re-seeds the subtree automatically.
*/ */
function libraryFingerprint(): string { function libraryFingerprint(): string {
const items = [ const items = EMBEDDED_LIBRARY.map((p) => `${p.folder}/${p.name}`)
...BUILTIN_PRESETS.map((p) => `root/embedded/Synth-C/${p.name}`),
...EMBEDDED_LIBRARY.map(
(p) => `${p.folder.replace('root/embedded/root/embedded/', 'root/embedded/')}/${p.name}`,
),
]
return items.sort().join('|') return items.sort().join('|')
} }
@ -72,30 +61,20 @@ async function ensureSeeded(): Promise<void> {
], ],
}, },
}) })
// the curated library has NO subfolders — drop any stale category
// folder rows (Synth-C / Amp Picks / Dynamics / Envision / Zai-Magic)
await db.folder.deleteMany({ where: { path: { startsWith: 'root/embedded/' } } })
await db.preset.createMany({ await db.preset.createMany({
data: [ data: EMBEDDED_LIBRARY.map((p) => ({
...BUILTIN_PRESETS.map((p) => ({ name: p.name,
name: p.name, author: p.author,
author: p.author, description: p.description,
description: p.description, content: p.content,
content: p.content, originalFile: p.originalFile ?? null,
originalFile: p.originalFile ?? null, isBuiltIn: true,
isBuiltIn: true, approx: p.approx ?? false,
approx: false, folderPath: p.folder,
folderPath: 'root/embedded/Synth-C', })),
})),
...EMBEDDED_LIBRARY.map((p) => ({
name: p.name,
author: p.author,
description: p.description,
content: p.content,
originalFile: p.originalFile ?? null,
isBuiltIn: true,
approx: p.approx ?? false,
// tolerate old converter output that recorded the download nesting
folderPath: p.folder.replace('root/embedded/root/embedded/', 'root/embedded/'),
})),
],
}) })
await db.settings.upsert({ await db.settings.upsert({
where: { id: 'main' }, where: { id: 'main' },
@ -114,6 +93,12 @@ async function ensureSeeded(): Promise<void> {
* the embedded library, folders outside the structural backbone — is purged * the embedded library, folders outside the structural backbone — is purged
* here so no visitor can ever alter what other visitors see. Runs on every * here so no visitor can ever alter what other visitors see. Runs on every
* GET; it's two indexed deleteMany calls on a small SQLite table. * GET; it's two indexed deleteMany calls on a small SQLite table.
*
* v7.19.0: the embedded branch ships NO subfolders, so the folder purge now
* keeps only the three backbone rows and deletes everything else (stale
* category folders included). User folders never exist server-side — the
* "root/user created" tree lives in each visitor's browser — so nothing of
* any visitor's is touched.
*/ */
async function purgeVisitorJunk(): Promise<void> { async function purgeVisitorJunk(): Promise<void> {
await db.preset.deleteMany({ await db.preset.deleteMany({
@ -126,14 +111,7 @@ async function purgeVisitorJunk(): Promise<void> {
}, },
}) })
await db.folder.deleteMany({ await db.folder.deleteMany({
where: { where: { path: { notIn: [ROOT, EMBEDDED_ROOT, USER_ROOT] } },
NOT: [
{ path: ROOT },
{ path: EMBEDDED_ROOT },
{ path: USER_ROOT },
{ path: { startsWith: 'root/embedded/' } },
],
},
}) })
} }

View file

@ -102,19 +102,17 @@ function useInitialLoad() {
const beat = readStoredBeatConfig() const beat = readStoredBeatConfig()
if (beat) store.applyBeatConfig(beat) if (beat) store.applyBeatConfig(beat)
// startup script: saved default (this browser) → v7.2 default preset // startup script: saved default (this browser) → the embedded default
// (Zai-Magic "Bass Bloom" — the megabuf particle bloom that remembers // preset (v7.19.0: "Beat Matrix" — the 16-band beat-reactive spectrum
// every beat's hue) → any Zai-Magic preset → template // wall, the single preset of the curated embedded library) → template
let text: string | null = lsGetString('ava.defaultScript') let text: string | null = lsGetString('ava.defaultScript')
const usedSavedDefault = text !== null && text.trim() !== '' const usedSavedDefault = text !== null && text.trim() !== ''
let startupPreset: (typeof presetsList)[number] | undefined let startupPreset: (typeof presetsList)[number] | undefined
if (!text) { if (!text) {
startupPreset = startupPreset =
presetsList.find( presetsList.find(
(p) => p.folderPath === 'root/embedded/Zai-Magic' && p.name === 'Bass Bloom', (p) => p.folderPath === 'root/embedded' && p.name === 'Beat Matrix',
) ?? ) ?? presetsList[0]
presetsList.find((p) => p.folderPath === 'root/embedded/Zai-Magic') ??
presetsList[0]
text = startupPreset?.content ?? null text = startupPreset?.content ?? null
} }
if (!text) text = STARTER_TEMPLATE if (!text) text = STARTER_TEMPLATE

View file

@ -320,17 +320,18 @@ function fileErrorMessage(err: unknown): string {
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// v7.2 showcase defaults — Bass Bloom + the Zai-Magic club mix // Showcase defaults (v7.19.0) — "Beat Matrix" over the demo track
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/** The read-only folder that ships the hand-authored Zai-Magic presets. */ /** The read-only folder of the curated embedded library (one preset:
const ZAI_MAGIC_FOLDER = 'root/embedded/Zai-Magic' * "Beat Matrix" — the single shipped preset, directly under root/embedded). */
const EMBEDDED_FOLDER = 'root/embedded'
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// v7.11 — the visitor's own selection vs. the welcome showcase // v7.11 — the visitor's own selection vs. the welcome showcase
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/** The script text this visit booted with (saved default / Bass Bloom / /** The script text this visit booted with (saved default / Beat Matrix /
* template). Any later difference = the visitor's own material. */ * template). Any later difference = the visitor's own material. */
let bootScriptSnapshot = '' let bootScriptSnapshot = ''
@ -366,18 +367,18 @@ export function visitorHasOwnSelection(): boolean {
/** /**
* Music just started but no preset is active (the visitor never picked one — * Music just started but no preset is active (the visitor never picked one —
* e.g. they are running their own untitled draft): load the default preset, * e.g. they are running their own untitled draft): load the default preset,
* Zai-Magic "Bass Bloom", so something beat-reactive is on screen the moment * the embedded "Beat Matrix", so something beat-reactive is on screen the
* audio arrives. Engine-only — the editor's unsaved text is never touched * moment audio arrives. Engine-only — the editor's unsaved text is never
* (the same courtesy random switching pays). v7.11: a visitor with their OWN * touched (the same courtesy random switching pays). v7.11: a visitor with
* material (custom draft / saved default) is left completely alone — the * their OWN material (custom draft / saved default) is left completely
* forced default used to replace their running script. * alone — the forced default used to replace their running script.
*/ */
function ensureDefaultPreset(): void { function ensureDefaultPreset(): void {
const s = useAvaStore.getState() const s = useAvaStore.getState()
if (s.activePresetId !== null) return if (s.activePresetId !== null) return
if (visitorHasOwnSelection()) return if (visitorHasOwnSelection()) return
const preset = s.presets.find( const preset = s.presets.find(
(p) => p.folderPath === ZAI_MAGIC_FOLDER && p.name === 'Bass Bloom', (p) => p.folderPath === EMBEDDED_FOLDER && p.name === 'Beat Matrix',
) )
if (!preset) return if (!preset) return
const v = validateScript(preset.content) const v = validateScript(preset.content)
@ -390,30 +391,31 @@ function ensureDefaultPreset(): void {
} }
/** /**
* The Demo button is the showcase: point the preset tree at Zai-Magic (and * The Demo button is the showcase: point the preset tree at the embedded
* expand it so the visitor sees what is playing), then start the sequence — * library (and expand it so the visitor sees what is playing). v7.19.0: the
* v7.4: every Zai-Magic preset IN ORDER, one every 20 seconds, wrapping * curated library is a single preset ("Beat Matrix"), so there is nothing
* around, for the one play-through (Sequence, not Random — the demo pass * to cycle — no mix timer is engaged; the track simply plays under the
* should walk the whole family, not repeat picks). v7.11: only ever called * beat-reactive wall, and any mix the visitor starts later is scoped to
* for a visit that made no choice of their own (see visitorHasOwnSelection). * the folder by the selection. v7.11: only ever called for a visit that
* made no choice of their own (see visitorHasOwnSelection).
*/ */
function startShowcaseMix(): void { function startShowcaseMix(): void {
const s = useAvaStore.getState() const s = useAvaStore.getState()
if (s.folders.includes(ZAI_MAGIC_FOLDER)) { if (s.folders.includes(EMBEDDED_FOLDER)) {
s.selectFolder(ZAI_MAGIC_FOLDER) // also scopes the mix to the folder s.selectFolder(EMBEDDED_FOLDER) // also scopes any later mix to the folder
s.toggleFolder(ZAI_MAGIC_FOLDER, true) s.toggleFolder(EMBEDDED_FOLDER, true)
} }
s.setRandomIntervalSec(20)
s.setSequenceMode(true) // mutual exclusion unticks Random if it was on
} }
/** /**
* v7.3/v7.4: the demo is a single pass — when it ends (natural play-through, * v7.3/v7.4: the demo is a single pass — when it ends (natural play-through,
* Stop button, or switching to another source) unwind the showcase state: * Stop button, or switching to another source) unwind the showcase state:
* the Demo flow force-checked the mix mode (Sequence), so it gets unchecked * the Demo flow used to force-check the mix mode (Sequence), so it gets
* again. The visitor's own selection (the last preset that was running) * unchecked again. v7.19.0 no longer engages a timer, but the unwind stays
* stays put. v7.11: only called when the showcase actually engaged — a * as a safety net for any flags that did get set. The visitor's own
* visitor whose own Random/Sequence mix was already running keeps it. * selection (the last preset that was running) stays put. v7.11: only
* called when the showcase actually engaged — a visitor whose own
* Random/Sequence mix was already running keeps it.
*/ */
function retireDemoShowcase(): void { function retireDemoShowcase(): void {
const s = useAvaStore.getState() const s = useAvaStore.getState()
@ -473,7 +475,7 @@ export function useAudioSource() {
const index = readDemoNext() const index = readDemoNext()
try { try {
await engine.audio.startDemo(index) await engine.audio.startDemo(index)
// v7.11: the welcome showcase (Bass Bloom + Zai-Magic Sequence) only // v7.11: the welcome showcase (Beat Matrix over the demo track) only
// engages for a visit that made NO choice of their own — no preset // engages for a visit that made NO choice of their own — no preset
// loaded (double-click / Load / a copy), no custom script material in // loaded (double-click / Load / a copy), no custom script material in
// the editor, no own mix running. Everyone else keeps their selection: // the editor, no own mix running. Everyone else keeps their selection:
@ -492,17 +494,17 @@ export function useAudioSource() {
if (reason === 'ended' || reason === 'stop') writeDemoNext(index + 1) if (reason === 'ended' || reason === 'stop') writeDemoNext(index + 1)
} }
syncSource() syncSource()
// showcase flow (v7.2/v7.4/v7.11): nothing active and nothing of the // showcase flow (v7.2/v7.11, reworked v7.19.0): nothing active and
// visitor's own → Bass Bloom, then the Zai-Magic sequence takes over — // nothing of the visitor's own → Beat Matrix on screen; the tree points
// every preset in order, 20 s each, for this one play-through. With // at the embedded library for the rest of the pass. With the visitor's
// the visitor's own selection, BOTH are skipped (ensureDefaultPreset // own selection, the showcase is skipped (ensureDefaultPreset holds its
// holds its own guard as well). // own guard as well).
ensureDefaultPreset() ensureDefaultPreset()
if (showcase) startShowcaseMix() if (showcase) startShowcaseMix()
toast({ toast({
title: 'Demo track playing', title: 'Demo track playing',
description: showcase description: showcase
? `${demoTrackTitle(index)} (${index + 1} of ${DEMO_TRACK_COUNT}) · Zai-Magic preset sequence -- every preset in order, 20 s each, one play-through.` ? `${demoTrackTitle(index)} (${index + 1} of ${DEMO_TRACK_COUNT}) · "Beat Matrix" is running -- the built-in beat-reactive wall, straight from the embedded library.`
: `${demoTrackTitle(index)} (${index + 1} of ${DEMO_TRACK_COUNT}) · your selection stays active -- the demo never touches your preset or the editor.`, : `${demoTrackTitle(index)} (${index + 1} of ${DEMO_TRACK_COUNT}) · your selection stays active -- the demo never touches your preset or the editor.`,
}) })
} catch (err) { } catch (err) {

View file

@ -110,8 +110,8 @@ export function VisualStage({ className }: { className?: string }) {
</h2> </h2>
<div className="grid gap-1.5 sm:gap-2.5"> <div className="grid gap-1.5 sm:gap-2.5">
{/* v7.2 showcase entry point -- the shimmering Demo Track {/* v7.2 showcase entry point -- the shimmering Demo Track
button: one click starts the music AND the Zai-Magic button: one click starts the music AND the embedded
club mix (random preset every 20 s) */} "Beat Matrix" showcase (v7.19.0: single-preset library) */}
<Button <Button
onClick={() => void startDemo()} onClick={() => void startDemo()}
className="shimmer-cta h-11 gap-2 bg-emerald-400 text-emerald-950 hover:bg-emerald-300 sm:h-12" className="shimmer-cta h-11 gap-2 bg-emerald-400 text-emerald-950 hover:bg-emerald-300 sm:h-12"

File diff suppressed because it is too large Load diff

View file

@ -1,18 +1,10 @@
// AVA — built-in preset scripts // AVA — starter template for new visualization scripts.
// //
// These five presets are adaptations of the real Winamp AVS presets the user // v7.19.0: the five Synth-C "built-in" presets (BUILTIN_PRESETS, formerly
// supplied (Google Drive → visbot.net scene classics). The core math of each // seeded under root/embedded/Synth-C) were removed when the embedded branch
// original Superscope / Texer II concept is preserved and translated to the // was curated down to just "Beat Matrix" (see lib/ava/embedded-presets.ts).
// AVA expression dialect. The original binary .avs files ship alongside the // This module now carries only the starter template used by the editor and
// app in /avs-originals/ for reference and download. // the new-preset flows.
export interface BuiltinPreset {
name: string
author: string
description: string
originalFile?: string
content: string
}
export const STARTER_TEMPLATE = `# AVA visualization script -- starter template export const STARTER_TEMPLATE = `# AVA visualization script -- starter template
# Tap "Help" in the editor for the full variable & function reference. # Tap "Help" in the editor for the full variable & function reference.
@ -45,227 +37,3 @@ red = 0.2 + bass
green = 0.8 green = 0.8
blue = 0.25 + treb blue = 0.25 + treb
` `
export const BUILTIN_PRESETS: BuiltinPreset[] = [
{
name: 'Gap The Mind',
author: 'synth-c (adapted)',
description:
'Radial waveform circles with the original polar-warp distortion. The radius tracks a rolling average of the audio, treble warps the ring geometry, and the hue sweeps on every beat. Adapted from the Winamp AVS classic (Visbot 17).',
originalFile: '/avs-originals/Synth-C - Gap The Mind.avs',
content: `# Adapted from "Synth-C - Gap The Mind" (Winamp AVS / visbot.net)
# Core of the original Superscope preserved:
# r = i*2pi; d = snd; x = sin(r)*d; y = cos(r)*d
# d = d*(1 - sin((r-pi/2)*7)*.03); r = r + cos(d*12)*.03
name: Gap The Mind
author: synth-c (adapted)
mode: lines
blend: additive
persist: 0.9
linewidth: 2
[init]
n = 480
hue = 0
rot = 0
avg = 0
[frame]
# rolling average of the audio energy (the original used a getosc ring buffer)
avg = avg * 0.8 + (bass + vol) * 0.2
hue = hue + dt * (0.05 + mid * 0.12) + beat * 0.015
rot = rot + dt * (0.1 + mid_att * 0.9)
rad = 0.25 + avg * 0.85
warp = 1 + treb * 0.5
[point]
r = i * 6.28318
d = rad + abs(v) * 0.45
d = d * (1 - sin((r - 1.5708) * 7) * 0.03 * warp)
r = r + cos(d * 12) * 0.03 * warp + rot
x = sin(r) * d / aspect
y = cos(r) * d
red = 0.5 + 0.5 * sin(hue * 6.28318)
green = 0.5 + 0.5 * sin(hue * 6.28318 + 2.094)
blue = 0.5 + 0.5 * sin(hue * 6.28318 + 4.188)
`,
},
{
name: "wonder 'bout",
author: 'zamuz (adapted)',
description:
'A field of rings that spawn, expand and fade -- each at its own tempo, sized by the bass, in stable per-ring colors (the original spawned Texer II ring sprites with random colors and bass-scaled sizes). From the visbot.net classic by zamuz.',
originalFile: '/avs-originals/zamuz - wonder bout.avs',
content: `# Adapted from "wonder 'bout" by zamuz (Winamp AVS / visbot.net)
# The original spawned Texer II ring sprites: ss = ss + .05 per frame,
# size = |ss| * min(w,h)*.006, random color & position on wrap,
# grow-scale sampled from the bass (getspec(.1,.1,0)).
name: wonder 'bout
author: zamuz (adapted)
mode: lines
blend: additive
persist: 0.88
linewidth: 1.8
[init]
n = 480
groups = 12
hue = 0
[frame]
# expansion speed rises with the smoothed bass (original: reg01 = .05/frame)
speed = 0.25 + bass_att * 0.9
hue = hue + dt * 0.1
[point]
g = floor(i * groups)
gi = frac(i * groups)
# per-ring phase, drifting at its own tempo
ph = frac(rand(g * 7.3 + 1) + time * speed * (0.7 + rand(g + 31) * 0.6))
px0 = rand(g + 3.7) * 1.7 - 0.85
py0 = rand(g + 9.1) * 1.7 - 0.85
rad = ph * (0.3 + bass * 0.6)
cr = rand(g + 21.7)
cg = rand(g + 15.3)
cb = rand(g + 27.9)
fade = (1 - ph) * (0.25 + ph * 2.2)
a = 6.28318 * (gi * 1.05 - 0.025)
x = px0 + cos(a) * rad / aspect
y = py0 + sin(a) * rad
red = cr * fade * (0.7 + 0.3 * sin(hue * 6.28318 + g))
green = cg * fade * (0.7 + 0.3 * sin(hue * 6.28318 + g + 2.09))
blue = cb * fade * (0.7 + 0.3 * sin(hue * 6.28318 + g + 4.19))
skip = if(below(gi, 0.025), 1, 0)
`,
},
{
name: 'Quantization',
author: 'synth-c (adapted)',
description:
'Audio-reactive rings snapped to a pixel grid -- the original quantized scope coordinates ((x*40)|0)/40 and rendered a sound heightmap as dot fog. Here the rings are quantized dots that thicken with the mid range.',
originalFile: '/avs-originals/Synth-C - Quantization.avs',
content: `# Adapted from "Synth-C - Quantization" (Winamp AVS / visbot.net)
# Original Superscope: r = i*2pi; d = snd + .5; x = sin(r)*d; y = cos(r)*d
# with pixel quantization x = ((x*40)|0)/40 and a dot-fog sound heightmap.
name: Quantization
author: synth-c (adapted)
mode: dots
blend: additive
persist: 0.82
linewidth: 3
[init]
n = 720
hue = 0
q = 34
avg = 0
[frame]
hue = hue + dt * 0.06
avg = avg * 0.8 + vol * 0.2
rad = 0.28 + avg * 0.75
zoom = 0.9 + bass * 0.3
[point]
r = i * 6.28318
d = rad + abs(v) * 0.4
x0 = sin(r) * d * zoom
y0 = cos(r) * d * zoom
# snap to the quantization grid (the |0 division from the original)
x = floor(x0 * q) / q / aspect
y = floor(y0 * q) / q
red = 0.5 + 0.5 * sin(hue * 6.28318 + d * 3)
green = 0.5 + 0.5 * sin(hue * 6.28318 + d * 3 + 2.09)
blue = 0.5 + 0.5 * sin(hue * 6.28318 + d * 3 + 4.19)
size = 2 + mid * 5
`,
},
{
name: "keep tryin'",
author: 'zamuz (adapted)',
description:
"Bass-driven particle bursts that expand outward with a sinusoidal swirl -- the original spawned white Texer dots with radius = bass*3+.3 and pushed the screen through sin(y*20) swirl movements. Dot size follows the highs.",
originalFile: '/avs-originals/zamuz - keep tryin.avs',
content: `# Adapted from "keep tryin'" by zamuz (Winamp AVS / visbot.net)
# Original Texer II dots: rs = getspec(.1,.1,0)*3 + .3 on spawn,
# swirl movement x = x + .002*sin(y*20); y = y + .002*sin(x*20).
name: keep tryin'
author: zamuz (adapted)
mode: dots
blend: additive
persist: 0.85
linewidth: 3
[init]
n = 900
travel = 0
hue = 0
[frame]
travel = travel + dt * (0.35 + bass_att * 1.2)
swirl = 0.15 + mid * 0.5
hue = hue + dt * 0.15
[point]
d = frac(rand(i + 0.13) + travel)
ang = rand(i + 0.77) * 6.28318 + d * swirl * 6.28318
rad = d * (0.3 + bass * 0.7)
x0 = sin(ang) * rad
y0 = cos(ang) * rad
# the swirl distortion from the original movement
x = x0 + 0.05 * sin(y0 * 20) / aspect
y = y0 + 0.05 * sin(x0 * 20)
fade = sin(d * 3.14159)
red = fade * (0.25 + 0.75 * sin(hue * 6.28318))
green = fade * (0.25 + 0.75 * sin(hue * 6.28318 + 2.09))
blue = fade * (0.25 + 0.75 * sin(hue * 6.28318 + 4.19))
size = 1.5 + treb * 3
`,
},
{
name: 'vorticity',
author: 'synth-c / zamuz (adapted)',
description:
'Swirling spiral arms rotating in a vortex -- the original advected 3D particle trails through a rotating vector field at sound-driven speed. Here six arms twist harder as the mid range rises and the whole vortex spins with the bass.',
originalFile: '/avs-originals/zamuz - vorticity brain activity remix.avs',
content: `# Adapted from "vorticity (brain activity remix)" -- original by synth-c,
# remix by zamuz (Winamp AVS / visbot.net). The original ran 3D particle
# trails through rotating vector fields at sound-driven speed (sndspd = 10).
name: vorticity
author: synth-c / zamuz (adapted)
mode: lines
blend: additive
persist: 0.92
linewidth: 1.6
[init]
n = 1200
t = 0
hue = 0
[frame]
t = t + dt * (0.4 + bass_att * 1.6)
twist = 1.2 + mid * 2.6
zoom = 0.75 + bass * 0.45
hue = hue + dt * 0.04 + beat * 0.01
[point]
arm = floor(i * 6)
fa = frac(i * 6)
d = fa
ang = arm * 1.047 + d * twist * 3.14159 + t
rad = (0.06 + d * d * 1.1) * zoom
x = sin(ang) * rad / aspect
y = cos(ang) * rad
red = (0.3 + 0.7 * d) * (0.35 + bass)
green = (0.3 + 0.7 * d) * (0.25 + mid)
blue = (0.3 + 0.7 * d) * (0.2 + treb + hue * 0.25)
skip = if(below(fa, 0.03), 1, 0)
`,
},
]
/** Download path for each original Winamp .avs file. */
export function originalFileUrl(name: string): string | undefined {
return BUILTIN_PRESETS.find((p) => p.name === name)?.originalFile
}

View file

@ -29,7 +29,7 @@ export interface PresetItem {
/** true = generated stand-in (embedded presets whose original relies on /** true = generated stand-in (embedded presets whose original relies on
* effects the script engine cannot run); false = original/user code. */ * effects the script engine cannot run); false = original/user code. */
approx?: boolean approx?: boolean
/** Full path from the virtual root, e.g. "root/embedded/Synth-C". */ /** Full path from the virtual root, e.g. "root/embedded". */
folderPath: string folderPath: string
/** Path of the original Winamp .avs binary in /public, when shipped. */ /** Path of the original Winamp .avs binary in /public, when shipped. */
originalFile: string | null originalFile: string | null

View file

@ -2714,3 +2714,38 @@ BROWSER E2E (agent-browser, ALL PASS):
Stage Summary: Stage Summary:
- v7.19.0 shipped: the trueblack tail is automatic. Every trail now ends in pure black at any persist without opting in, via an exact -1/255 GPU sweep that preserves alpha (behind/clear safe); the Canvas2D difference tail is demoted to a gated no-WebGL fallback; the header is a tri-state (auto/on/off). Texe-rings works unmodified -- the smear is gone and the background returns to pure black behind the rings. - v7.19.0 shipped: the trueblack tail is automatic. Every trail now ends in pure black at any persist without opting in, via an exact -1/255 GPU sweep that preserves alpha (behind/clear safe); the Canvas2D difference tail is demoted to a gated no-WebGL fallback; the header is a tri-state (auto/on/off). Texe-rings works unmodified -- the smear is gone and the background returns to pure black behind the rings.
---
Task ID: 53
Agent: main (Z.ai Code)
Task: v7.19.0 embedded-library curation (user request): clean out the "Embedded" branch -- remove every item and subfolder except one preset, "Beat Matrix", which moves up to live directly at root/embedded/Beat Matrix. "embedded" stays read-only; the "user created" branch stays untouched.
Library source changes:
- src/lib/ava/embedded-presets.ts: rewritten from 1476 lines / 90 presets (Amp Picks 66, Dynamics 9, Envision 16, Zai-Magic 12) + 5 Synth-C built-ins to a single hand-authored preset: Beat Matrix, folder "root/embedded" (was root/embedded/Zai-Magic). Script content byte-identical to the v7.13.1-fixed original (cell-center grid mapping, getspec bands, beat-rotated rainbow, strobe flash). EMBEDDED_SEED_VERSION 7.18.1 -> 7.19.0; header documents the curation + where the retired sources still live (tools/converted/, public/avs-originals/).
- src/lib/ava/presets.ts: stripped to STARTER_TEMPLATE only. BUILTIN_PRESETS (the five Synth-C ports formerly seeded to root/embedded/Synth-C), the BuiltinPreset interface and the dead originalFileUrl() helper removed; no importer remains (script-tab / page / audio-source / presets-tab only ever wanted STARTER_TEMPLATE).
Seed pipeline changes (src/app/api/presets/route.ts):
- BASE_FOLDERS: the five category rows dropped; just root / root/embedded / root/user created.
- libraryFingerprint()/seed data: BUILTIN branch gone; the old doubled-path replace() (converter "root/embedded/root/embedded/" nesting tolerance) removed -- the curated library carries clean folders.
- Re-seed now also deletes stale embedded subfolder rows (deleteMany startsWith "root/embedded/"), so Synth-C / Amp Picks / Dynamics / Envision / Zai-Magic Folder rows do not linger as empty tree nodes.
- purgeVisitorJunk folder purge inverted: it used to KEEP everything under root/embedded/; it now keeps ONLY the three backbone rows (notIn), enforcing the no-subfolders embedded branch on every GET. User folders never exist server-side (browser localStorage), so nothing of any visitor's is touched.
Startup / showcase rewiring (the old hardwires pointed at the removed folder):
- page.tsx: startup preset = Beat Matrix at root/embedded (was Bass Bloom in Zai-Magic, then any Zai-Magic, then [0]).
- audio-source.ts: ZAI_MAGIC_FOLDER -> EMBEDDED_FOLDER ("root/embedded"); ensureDefaultPreset loads Beat Matrix; startShowcaseMix no longer engages a 20 s Sequence timer (nothing to cycle with a single preset) -- it just selects + expands the embedded folder, which scopes any later mix; retireDemoShowcase kept as a safety no-op; demo toast rewritten ("your selection stays active" / "\"Beat Matrix\" is running -- the built-in beat-reactive wall, straight from the embedded library.").
- visual-stage.tsx demo-button comment + store.ts folderPath doc example + prisma/schema.prisma Preset/Settings comments updated off the retired structure (comment-only; no db push needed).
Data:
- First GET after the edit self-healed via drift detection: "[presets] embedded library drift detected -- re-seeding from source". Direct Prisma inspection after: PRESETS = ["root/embedded/Beat Matrix (builtIn=true)"], FOLDERS = ["root","root/embedded","root/user created"], embeddedSeed = 7.19.0.
- db/custom.db.seed re-synced from the live db/custom.db (project convention: the seed file is the sandbox-reprovision baseline).
BROWSER E2E (agent-browser, ALL PASS):
- Tree: root (read-only) > embedded (read-only, "1 preset . 0 subfolders", READ-ONLY lock) > Beat Matrix; user created sibling intact; header "1 presets . 3 folders". No empty ex-category nodes.
- Double-click Beat Matrix: runs (footer SCRIPT: Beat Matrix, PTS 160 = the 16x10 grid).
- Fresh-visitor demo showcase (localStorage cleared): Demo Track -> SRC DEMO, BPM locked 131.4, Beat Matrix auto-loaded, toast shows the new showcase text, tree scoped "Random from: embedded" with embedded selected + expanded; no Sequence timer engaged.
- Returning-visitor branch: after a manual load, Demo toast shows "your selection stays active" (v7.11 guard intact).
- User-created branch: "Copy to my presets" on the embedded Beat Matrix -> lands under root/user created (tree "user created 1"), persisted in ove.userPresets.v1 localStorage; embedded branch untouched.
- No page errors / console warnings; bun run lint clean; mobile 390x844 footer pushed naturally with content (no overlap).
Stage Summary:
- The Embedded branch is now exactly root/embedded/Beat Matrix: one hand-authored preset, zero subfolders, still read-only (server refuses all mutations; per-GET purge enforces the shape). The 95 retired presets and their folders are gone from the tree and the seed baseline; the "user created" branch is unchanged and verified working end-to-end.