diff --git a/db/custom.db b/db/custom.db
index a9fd39d..fc48a15 100755
Binary files a/db/custom.db and b/db/custom.db differ
diff --git a/rust-agent/src/spectrum.rs b/rust-agent/src/spectrum.rs
index 4a1d108..f8adf47 100755
--- a/rust-agent/src/spectrum.rs
+++ b/rust-agent/src/spectrum.rs
@@ -15,6 +15,12 @@ use rustfft::{Fft, FftPlanner};
/// engine's AnalyserNode.
pub const FFT_SIZE: usize = 8192;
+/// Top of the script-visible spectrum, in Hz. Mirrors SPEC_TOP_HZ in
+/// src/lib/ava/audio.ts (v7.10.1): the bar field stops at 16 kHz instead of
+/// Nyquist because music and every lossy codec are silent above that, which
+/// used to freeze the right edge of spectrum visuals.
+pub const SPEC_TOP_HZ: f32 = 16000.0;
+
/// Waveform history kept for the script `v` variable (matches the web
/// engine's "most recent 2048 samples" window).
pub const WAVE_SIZE: usize = 2048;
@@ -48,13 +54,18 @@ impl Spectrum {
}
/// Spectrum magnitude at position 0..1 → 0..1 with the web engine's
- /// log-ish frequency mapping. Mirrors `AudioEngine.getSpectrum()`.
+ /// log-ish frequency mapping. Mirrors `AudioEngine.getSpectrum()` in
+ /// audio.ts: the field tops out at `SPEC_TOP_HZ` (16 kHz), not Nyquist.
pub fn spectrum_at(&self, pos: f64) -> f64 {
if self.mags.is_empty() {
return 0.0;
}
let p = pos.clamp(0.0, 1.0);
- let bin = 2.0 + (p.powf(1.6) * (self.mags.len() as f64 - 3.0)).floor();
+ let bh = bin_hz(self.sample_rate, self.mags.len() * 2);
+ let hi = ((SPEC_TOP_HZ / bh).ceil() as usize)
+ .min(self.mags.len() - 1)
+ .max(3);
+ let bin = 2.0 + (p.powf(1.6) * (hi as f64 - 2.0)).floor();
self.mags[(bin as usize).min(self.mags.len() - 1)] as f64
}
}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 4627eb4..48c9767 100755
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -17,8 +17,9 @@ import { VisualStage } from '@/components/ava/visual-stage'
import { ControlPanel } from '@/components/ava/control-panel'
import { FooterBar } from '@/components/ava/footer-bar'
import { QueueBar } from '@/components/ava/queue-bar'
-import { applyScriptText, tapBeat } from '@/components/ava/audio-source'
+import { applyScriptText, setBootScriptSnapshot, tapBeat } from '@/components/ava/audio-source'
import { STARTER_TEMPLATE } from '@/lib/ava/presets'
+import { releaseWakeLock, syncWakeLock, watchWakeLockVisibility } from '@/lib/ava/wake-lock'
import {
lsGetString,
readStoredBandConfig,
@@ -105,6 +106,7 @@ function useInitialLoad() {
// (Zai-Magic "Bass Bloom" — the megabuf particle bloom that remembers
// every beat's hue) → any Zai-Magic preset → template
let text: string | null = lsGetString('ava.defaultScript')
+ const usedSavedDefault = text !== null && text.trim() !== ''
let startupPreset: (typeof presetsList)[number] | undefined
if (!text) {
startupPreset =
@@ -117,6 +119,12 @@ function useInitialLoad() {
}
if (!text) text = STARTER_TEMPLATE
applyScriptText(text)
+ // v7.11: remember the untouched startup script — the Demo showcase
+ // only auto-engages while the editor still matches it (any difference
+ // is the visitor's own material). A SAVED default is the visitor's
+ // own material from the start, so it counts as their choice.
+ setBootScriptSnapshot(text)
+ if (usedSavedDefault) store.markManualChoice()
if (startupPreset) store.setActivePresetId(startupPreset.id)
// club mode: bring back the random-preset timer if it was on
@@ -146,10 +154,28 @@ function useSpaceToTap() {
}, [])
}
+/**
+ * v7.11 — keep the phone's screen on while music is playing. Any source but
+ * idle holds a screen wake lock (see lib/ava/wake-lock.ts); the moment the
+ * engine goes idle the lock is released and the device's normal inactivity
+ * dimming / sleep takes over again. Browsers silently drop the sentinel when
+ * the page is hidden, so a visibilitychange watcher re-acquires it on return
+ * while music is still going. Unsupported browsers degrade silently.
+ */
+function useWakeLockOnPlayback() {
+ const source = useAvaStore((s) => s.source)
+ useEffect(() => {
+ syncWakeLock(source !== 'idle')
+ }, [source])
+ useEffect(() => watchWakeLockVisibility(), [])
+ useEffect(() => () => releaseWakeLock(), [])
+}
+
export default function Home() {
useInitialLoad()
useBeatWatcher()
useSpaceToTap()
+ useWakeLockOnPlayback()
return (
diff --git a/src/components/ava/audio-source.ts b/src/components/ava/audio-source.ts
index e6d43ae..bbc87cc 100755
--- a/src/components/ava/audio-source.ts
+++ b/src/components/ava/audio-source.ts
@@ -11,6 +11,7 @@ import { getEngine } from '@/lib/ava/engine'
import type { TapResult } from '@/lib/ava/beat'
import { validateScript } from '@/lib/ava/script'
import type { ScriptError } from '@/lib/ava/script'
+import { STARTER_TEMPLATE } from '@/lib/ava/presets'
import { DEMO_TRACK_COUNT, demoTrackTitle } from '@/lib/ava/audio'
import { useAvaStore } from '@/lib/store'
import { toast, useToast } from '@/hooks/use-toast'
@@ -325,16 +326,56 @@ function fileErrorMessage(err: unknown): string {
/** The read-only folder that ships the hand-authored Zai-Magic presets. */
const ZAI_MAGIC_FOLDER = 'root/embedded/Zai-Magic'
+// ---------------------------------------------------------------------------
+// v7.11 — the visitor's own selection vs. the welcome showcase
+// ---------------------------------------------------------------------------
+
+/** The script text this visit booted with (saved default / Bass Bloom /
+ * template). Any later difference = the visitor's own material. */
+let bootScriptSnapshot = ''
+
+/** Capture the startup script (page boot) — the "untouched editor" baseline. */
+export function setBootScriptSnapshot(text: string): void {
+ bootScriptSnapshot = text.trim()
+}
+
+/**
+ * v7.11 — does this visitor have their OWN thing going? True when they
+ * loaded a preset (tree Load / double-click / Copy to my presets / created
+ * one), brought their own script material into the editor (typed, pasted,
+ * hash-imported, file-loaded — anything that differs from the untouched
+ * startup text), saved their own default startup script, or are running
+ * their own mix mode. The Demo showcase auto-engages ONLY while this is
+ * false; otherwise the Demo button just plays music and touches nothing —
+ * a custom script can be tested and debugged against the demo track
+ * without any fear of it being replaced.
+ */
+export function visitorHasOwnSelection(): boolean {
+ const s = useAvaStore.getState()
+ if (s.manualChoice) return true
+ // their own club-mode mix is running — the demo must not re-scope it
+ if (s.randomize || s.sequenceMode) return true
+ const cur = s.scriptText.trim()
+ if (!cur) return false
+ // the untouched starter template (fresh visit / New button) is not own material
+ if (cur === STARTER_TEMPLATE.trim()) return false
+ // anything else that differs from the untouched startup text is theirs
+ return cur !== bootScriptSnapshot
+}
+
/**
* 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,
* Zai-Magic "Bass Bloom", so something beat-reactive is on screen the moment
* audio arrives. Engine-only — the editor's unsaved text is never touched
- * (the same courtesy random switching pays).
+ * (the same courtesy random switching pays). v7.11: a visitor with their OWN
+ * material (custom draft / saved default) is left completely alone — the
+ * forced default used to replace their running script.
*/
function ensureDefaultPreset(): void {
const s = useAvaStore.getState()
if (s.activePresetId !== null) return
+ if (visitorHasOwnSelection()) return
const preset = s.presets.find(
(p) => p.folderPath === ZAI_MAGIC_FOLDER && p.name === 'Bass Bloom',
)
@@ -353,7 +394,8 @@ function ensureDefaultPreset(): void {
* expand it so the visitor sees what is playing), then start the sequence —
* v7.4: every Zai-Magic preset IN ORDER, one every 20 seconds, wrapping
* around, for the one play-through (Sequence, not Random — the demo pass
- * should walk the whole family, not repeat picks).
+ * should walk the whole family, not repeat picks). v7.11: only ever called
+ * for a visit that made no choice of their own (see visitorHasOwnSelection).
*/
function startShowcaseMix(): void {
const s = useAvaStore.getState()
@@ -370,7 +412,8 @@ function startShowcaseMix(): void {
* Stop button, or switching to another source) unwind the showcase state:
* the Demo flow force-checked the mix mode (Sequence), so it gets unchecked
* again. The visitor's own selection (the last preset that was running)
- * stays put.
+ * 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 {
const s = useAvaStore.getState()
@@ -430,27 +473,37 @@ export function useAudioSource() {
const index = readDemoNext()
try {
await engine.audio.startDemo(index)
+ // v7.11: the welcome showcase (Bass Bloom + Zai-Magic Sequence) only
+ // engages for a visit that made NO choice of their own — no preset
+ // loaded (double-click / Load / a copy), no custom script material in
+ // the editor, no own mix running. Everyone else keeps their selection:
+ // Demo just plays the track.
+ const showcase = !visitorHasOwnSelection()
// v7.3/v7.10: single pass — when the track runs out (or is stopped, or
// replaced by another source) the engine fires onDemoEnded exactly
// once, WITH the reason: sync the store to idle (the landing overlay
- // returns), uncheck the randomizer the showcase had checked, and —
- // when the pass truly ENDED (natural finish or the Stop button) —
+ // returns), uncheck the showcase mix ONLY when the showcase engaged,
+ // and — when the pass truly ENDED (natural finish or the Stop button) —
// advance the collection place so the next Demo click plays the next
// track. A source switch keeps the visitor's place.
engine.audio.onDemoEnded = (reason) => {
syncSource()
- retireDemoShowcase()
+ if (showcase) retireDemoShowcase()
if (reason === 'ended' || reason === 'stop') writeDemoNext(index + 1)
}
syncSource()
- // showcase flow (v7.2/v7.4): nothing active → Bass Bloom, then the
- // Zai-Magic sequence takes over — every preset in order, 20 s each,
- // for this one play-through
+ // showcase flow (v7.2/v7.4/v7.11): nothing active and nothing of the
+ // visitor's own → Bass Bloom, then the Zai-Magic sequence takes over —
+ // every preset in order, 20 s each, for this one play-through. With
+ // the visitor's own selection, BOTH are skipped (ensureDefaultPreset
+ // holds its own guard as well).
ensureDefaultPreset()
- startShowcaseMix()
+ if (showcase) startShowcaseMix()
toast({
title: 'Demo track playing',
- description: `${demoTrackTitle(index)} (${index + 1} of ${DEMO_TRACK_COUNT}) · Zai-Magic preset sequence -- every preset in order, 20 s each, one play-through.`,
+ 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}) · your selection stays active -- the demo never touches your preset or the editor.`,
})
} catch (err) {
syncSource()
diff --git a/src/components/ava/help-panel.tsx b/src/components/ava/help-panel.tsx
index 8b9ebae..d008bc2 100755
--- a/src/components/ava/help-panel.tsx
+++ b/src/components/ava/help-panel.tsx
@@ -23,12 +23,15 @@ const FRAME_VARS: [string, string][] = [
['w / h', 'canvas size in pixels'],
['aspect', 'canvas width / height -- divide x by it for perfect circles'],
['persist', 'motion-trail persistence 0..0.98 (writable per frame)'],
+ ['bgrot', 'background spin in radians per frame -- + clockwise, - counter-clockwise; 0 = off (set in [init] / [frame] / [beat]; persists like any variable)'],
+ ['bgx / bgy', 'background drift per frame, the -1..1 screen convention (y is up) -- slide the whole picture up, down, left or right'],
+ ['bgzoom', 'background scale per frame -- below 1 draw-in (shrinks toward the center), above 1 blow-out (expands); 1 = off'],
]
const POINT_VARS: [string, string][] = [
['i', '0..1 point index'],
['v', 'waveform sample −1..1'],
- ['sv', 'spectrum magnitude 0..1'],
+ ['sv', 'spectrum magnitude 0..1 (≈12 Hz to 16 kHz, log-ish -- the range music actually occupies)'],
['x / y', 'position −1..1 (x = sin(a)*rad / aspect keeps circles round)'],
['red / green / blue', 'point color 0..1'],
['skip', '> 0.5 hides the point / breaks the line'],
@@ -62,6 +65,9 @@ const EXAMPLES: [string, string][] = [
['rot = rot + dt * 1.2 + beat * 0.4', 'beat kick'],
['ang = rand(i) * 6.28318', 'stable per-point random'],
['x = sin(i * 6.28318 + t) * rad / aspect', 'circle'],
+ ['bgrot = dir * (0.004 + exp(-beatage * 3) * 0.02)', 'the background spins to the music -- every beat accelerates it, then it relaxes (slow persist blends the trails)'],
+ ['if(above(bass, bass_att * 1.25), dir = -dir, 0)', 'a hard hit flips the background spin direction -- put this in [beat]'],
+ ['bgzoom = 1 - 0.001', 'draw-in -- the picture slowly spirals toward the center'],
]
function HelpTable({ rows, mono = true }: { rows: [string, string][]; mono?: boolean }) {
diff --git a/src/components/ava/presets-tab.tsx b/src/components/ava/presets-tab.tsx
index ed79cd2..20ddfc1 100755
--- a/src/components/ava/presets-tab.tsx
+++ b/src/components/ava/presets-tab.tsx
@@ -167,6 +167,9 @@ function useLoadPreset() {
if (result.ok) {
st.setActivePresetId(preset.id)
st.setEditTarget({ id: preset.id, name: preset.name, folderPath: preset.folderPath })
+ // v7.11: an explicitly loaded preset (double-click / Load button) is
+ // the visitor's own choice — the Demo showcase must never override it
+ st.markManualChoice()
// No "Preset loaded" toast (v7.3): the footer's SCRIPT: label
// already echoes the running preset — the popup was intrusive and
// only compile errors genuinely need the attention grab.
@@ -527,6 +530,7 @@ export function PresetsTab() {
applyScriptText(STARTER_TEMPLATE)
st.setActivePresetId(res.preset.id)
st.selectPreset(res.preset.id)
+ st.markManualChoice() // v7.11 — a created preset is the visitor's own choice
st.setTab('script')
toast({
title: 'Preset created',
@@ -671,6 +675,7 @@ export function PresetsTab() {
const st = useAvaStore.getState()
st.toggleFolder(USER_ROOT, true)
st.selectPreset(res.preset.id)
+ st.markManualChoice() // v7.11 — a copied preset is the visitor's own choice
toast({
title: 'Copied to your presets',
description: `"${res.preset.name}" is now yours to edit -- stored in this browser only.`,
diff --git a/src/components/ava/script-tab.tsx b/src/components/ava/script-tab.tsx
index f79874b..f31de10 100755
--- a/src/components/ava/script-tab.tsx
+++ b/src/components/ava/script-tab.tsx
@@ -1,7 +1,9 @@
// AVA — Script tab: the custom visualization script editor.
//
-// Toolbar: Apply, Save (to the preset picked in the Presets tab, when a save
-// target is set), Save As…, Save as Default, Save to File, Load from File,
+// Toolbar: Apply, Save (to the preset picked in the Presets tab when a save
+// target is set; v7.11 also with NO target — one click stores the current
+// script as a new preset under "user created", auto-named from its name:
+// header), Save As…, Save as Default, Save to File, Load from File,
// Script⇢Hash / Hash⇢Script (v7.7 — share the script as one short Unicode
// string and paste one back), Upload to OVE-Directory (v7.11 — publish the
// compiled script to the community directory), New, Help. Editor: line-number
@@ -51,7 +53,7 @@ import { sanitizeFileName } from '@/components/ava/shared'
import { useToast } from '@/hooks/use-toast'
import { cn } from '@/lib/utils'
import { USER_ROOT, isUserAreaPath } from '@/lib/preset-paths'
-import { createUserPreset, presetIsLocal, updateUserPreset } from '@/lib/ava/user-presets'
+import { createUserPreset, listUserPresets, presetIsLocal, renameUserPreset, updateUserPreset } from '@/lib/ava/user-presets'
/** Middle-truncated save-target path for the chip: "root/embedded/…/Name". */
function formatSavePath(folderPath: string, name: string): string {
@@ -61,6 +63,16 @@ function formatSavePath(folderPath: string, name: string): string {
return `${segs.slice(0, 2).join('/')}/…/${name}`
}
+/** First free name in a folder: "Base", "Base (1)", "Base (2)" … */
+function uniqueName(base: string, taken: Set
): string {
+ if (!taken.has(base)) return base
+ for (let i = 1; i < 1000; i++) {
+ const candidate = `${base} (${i})`
+ if (!taken.has(candidate)) return candidate
+ }
+ return `${base} (${Date.now().toString(36).slice(-4)})`
+}
+
export function ScriptTab() {
const { toast } = useToast()
const scriptText = useAvaStore((s) => s.scriptText)
@@ -89,6 +101,17 @@ export function ScriptTab() {
// earlier Apply keeps this off until Apply runs clean).
const [uploadOpen, setUploadOpen] = useState(false)
const canUpload = validation?.ok === true && !runtimeError
+ // v7.11 (parallel line, merged in v7.13) — the display name a Hash⇢Script
+ // import arrived with (the script's name: header, else the share-string
+ // label); the one-click Save uses it when the script itself carries no name
+ const [pendingName, setPendingName] = useState(null)
+ // v7.11 (parallel line, merged in v7.13) — "Give This Preset a Name" dialog
+ // (renames the just-saved auto-named preset: nameless scripts / duplicates)
+ const [renameOpen, setRenameOpen] = useState(false)
+ const [renameValue, setRenameValue] = useState('')
+ const [renameError, setRenameError] = useState(null)
+ const [renameBusy, setRenameBusy] = useState(false)
+ const [renameTarget, setRenameTarget] = useState<{ id: string; name: string; folderPath: string } | null>(null)
// debounced live validation (compile is cheap, scripts are small)
useEffect(() => {
@@ -168,6 +191,111 @@ export function ScriptTab() {
}
}
+ /**
+ * v7.11 — one-click save with NO target picked: create a new preset under
+ * "user created" from the current editor script. Auto-named from the
+ * script's name: header (e.g. a hash import of "Spectrum Bars:[▼…]" saves
+ * as "Spectrum Bars"); a nameless script lands as "Empty Name", a
+ * conflict as "Base (1)", "Base (2)"… — and whenever the name had to be
+ * invented, the "Give This Preset a Name" dialog opens right after so the
+ * just-saved preset can be renamed in one extra step. Nothing is ever
+ * lost to a naming problem: the preset is saved FIRST, renamed second.
+ */
+ const saveAsNewPreset = async () => {
+ if (saving) return
+ setSaving(true)
+ try {
+ const v = validateScript(scriptText)
+ if (!v.ok) {
+ toast({
+ title: 'Fix the compile errors first',
+ description: 'The script must compile before it can be saved as a preset.',
+ variant: 'destructive',
+ })
+ return
+ }
+ // candidate name: the script's own name: header, else the share-string
+ // label it arrived with (Hash⇢Script import)
+ let base: string | null = null
+ const metaName = v.meta?.name?.trim()
+ if (metaName && metaName !== 'Untitled') base = metaName
+ if (!base && pendingName) base = pendingName.trim() || null
+ if (base) {
+ // preset names must stay path-safe (no slashes / control characters)
+ base = base.replace(/[/\u0000-\u001f\u007f]+/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 120).trim()
+ if (!base) base = null
+ }
+ const taken = new Set(
+ listUserPresets().filter((p) => p.folderPath === USER_ROOT).map((p) => p.name),
+ )
+ const autoName = uniqueName(base ?? 'Empty Name', taken)
+ const invented = base === null || autoName !== base
+ const res = createUserPreset({
+ name: autoName,
+ content: scriptText,
+ folderPath: USER_ROOT,
+ author: v.meta?.author || 'you',
+ })
+ if (!res.ok) {
+ toast({ title: 'Save failed', description: res.error, variant: 'destructive' })
+ return
+ }
+ applyUserPresets()
+ setEditTarget({ id: res.preset.id, name: res.preset.name, folderPath: res.preset.folderPath })
+ const st = useAvaStore.getState()
+ st.selectPreset(res.preset.id)
+ st.toggleFolder(USER_ROOT, true)
+ toast({
+ title: `Saved as "${res.preset.name}"`,
+ description: invented
+ ? 'Stored under "user created" in this browser -- give it a better name in the dialog.'
+ : 'Stored under "user created" in this browser.',
+ })
+ if (invented) {
+ // the name had to be invented (nameless script / duplicate conflict)
+ // — the preset is SAFE; offer the rename as one extra step
+ setRenameTarget({ id: res.preset.id, name: res.preset.name, folderPath: res.preset.folderPath })
+ setRenameValue(base ?? '')
+ setRenameError(null)
+ setRenameOpen(true)
+ }
+ } finally {
+ setSaving(false)
+ }
+ }
+
+ /** v7.11 — confirm the "Give This Preset a Name" dialog: rename the
+ * just-saved preset to the visitor's name (stays open on conflicts). */
+ const applyRename = async () => {
+ if (!renameTarget || renameBusy) return
+ const trimmed = renameValue.trim()
+ if (!trimmed) {
+ setRenameError('Enter a name first.')
+ return
+ }
+ setRenameBusy(true)
+ try {
+ const res = renameUserPreset(renameTarget.id, trimmed)
+ if (!res.ok) {
+ setRenameError(res.error)
+ return
+ }
+ applyUserPresets()
+ const st = useAvaStore.getState()
+ if (st.editTarget?.id === res.preset.id) {
+ st.setEditTarget({ id: res.preset.id, name: res.preset.name, folderPath: res.preset.folderPath })
+ }
+ st.selectPreset(res.preset.id)
+ setRenameOpen(false)
+ toast({
+ title: 'Preset renamed',
+ description: `Saved as "${res.preset.name}" in ${res.preset.folderPath} (this browser).`,
+ })
+ } finally {
+ setRenameBusy(false)
+ }
+ }
+
const openSaveAs = () => {
if (!editTarget) return
setSaveAsName(editTarget.name)
@@ -235,6 +363,9 @@ export function ScriptTab() {
const text = await file.text()
const result = applyScriptText(text)
if (result.ok) {
+ // v7.11: a file from disk is the visitor's own material — the Demo
+ // showcase must never clobber it
+ useAvaStore.getState().markManualChoice()
const meta = validateScript(text).meta
toast({ title: 'Script loaded', description: `"${meta?.name || file.name}" applied to the engine.` })
} else if (result.error) {
@@ -310,7 +441,7 @@ export function ScriptTab() {
>
Apply
- {editTarget && (
+ {editTarget ? (
void saveToTarget()}
@@ -320,6 +451,18 @@ export function ScriptTab() {
>
Save
+ ) : (
+ validation?.ok && (
+ void saveAsNewPreset()}
+ disabled={saving}
+ className="h-9 gap-1.5 bg-emerald-400 px-3 text-emerald-950 hover:bg-emerald-300"
+ title='Save the current script as a NEW preset under "user created" -- auto-named from the script name: header'
+ >
+ Save
+
+ )
)}
{editTarget && (
{
setScriptText(STARTER_TEMPLATE)
setEditTarget(null)
+ setPendingName(null)
}}
className="h-9 gap-1.5 border-zinc-700"
title="Start over from the template (clears the save target)"
@@ -510,12 +654,73 @@ export function ScriptTab() {
+ {/* v7.11 -- rename the just-auto-named preset (nameless script /
+ duplicate conflict): the preset is already saved under "user
+ created"; this dialog only gives it the visitor's own name */}
+ {
+ setRenameOpen(o)
+ if (o) setRenameError(null)
+ }}
+ >
+
+
+ Give This Preset a Name
+
+ The script is already saved as “{renameTarget?.name ?? 'Untitled'}” under “user created” in this
+ browser. Name it anything you like -- the rename applies instantly.
+
+
+
+
{
+ setRenameValue(e.target.value)
+ setRenameError(null)
+ }}
+ placeholder="Preset name"
+ autoFocus
+ aria-label="New preset name"
+ className="h-9 border-zinc-700 bg-zinc-900 font-mono text-xs"
+ onFocus={(e) => e.currentTarget.select()}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') void applyRename()
+ }}
+ />
+ {renameError &&
{renameError}
}
+
+
+ setRenameOpen(false)}>
+ Keep as-is
+
+ void applyRename()}
+ >
+ {renameBusy ? 'Renaming…' : 'Save Name'}
+
+
+
+
+
{/* v7.7 -- share-string dialogs */}
setScriptText(text)}
+ onImport={(text, name) => {
+ setScriptText(text)
+ setPendingName(name)
+ // the editor now holds FOREIGN content — the previous save target
+ // no longer matches what is in the editor, so a later Save must not
+ // overwrite it (v7.11: Save creates a new preset instead)
+ setEditTarget(null)
+ // imported material is the visitor's own — the Demo showcase must
+ // never clobber it (v7.11)
+ useAvaStore.getState().markManualChoice()
+ }}
/>
{/* v7.11 -- publish to the community directory */}
diff --git a/src/components/ava/share-dialogs.tsx b/src/components/ava/share-dialogs.tsx
index 5ed71fa..9721d7b 100755
--- a/src/components/ava/share-dialogs.tsx
+++ b/src/components/ava/share-dialogs.tsx
@@ -164,8 +164,10 @@ export function HashToScriptDialog({
}: {
open: boolean
onOpenChange: (open: boolean) => void
- /** Load the decoded script into the editor (script-tab wires this). */
- onImport: (script: string) => void
+ /** Load the decoded script into the editor (script-tab wires this).
+ * v7.11: also hands back the script's display name (its name: header,
+ * else the share-string label) so Save can auto-name the new preset. */
+ onImport: (script: string, name: string | null) => void
}) {
const { toast } = useToast()
const [input, setInput] = useState('')
@@ -219,12 +221,12 @@ export function HashToScriptDialog({
function load() {
if (!decoded) return
- onImport(decoded.script)
+ onImport(decoded.script, decoded.name ?? null)
onOpenChange(false)
toast({
title: 'Script loaded from share string',
description: `"${decoded.name ?? 'Untitled'}" -- ${decoded.lines} lines in the editor.${
- decoded.compiles ? ' Hit Apply to run it.' : ' Fix the errors, then Apply.'
+ decoded.compiles ? ' Hit Apply to run it, or Save to keep it as a preset.' : ' Fix the errors, then Apply.'
}`,
})
}
diff --git a/src/lib/ava/audio.ts b/src/lib/ava/audio.ts
index 410e70e..f8c6f98 100755
--- a/src/lib/ava/audio.ts
+++ b/src/lib/ava/audio.ts
@@ -25,6 +25,19 @@ import type { AudioSourceKind, BandConfig, BandLevels } from './types'
import { ZERO_LEVELS } from './types'
const FFT_SIZE = 8192
+/**
+ * Top of the script-visible spectrum, in Hz (v7.10.1). The bar field
+ * (getspec()/sv) tops out HERE instead of at Nyquist (22/24 kHz): real
+ * music carries almost nothing above ~16 kHz, and every lossy source
+ * low-passes even lower (192 kbps MP3 ≈ 18.6 kHz, YouTube AAC ≈ 15.5 kHz;
+ * measured across the demo collection: 0.000% of total magnitude above
+ * 18.6 kHz). A full-range mapping therefore left the rightmost ~20% of
+ * every spectrum visual frozen at zero -- bars that never moved. 16 kHz
+ * matches the spectral-flux cap in getBands(), and puts the right edge on
+ * the 12-16 kHz "air" band where cymbals, hats and synth sparkle live.
+ * Mirrored by SPEC_TOP_HZ in rust-agent/src/spectrum.rs.
+ */
+const SPEC_TOP_HZ = 16000
const ATTACK_TAU = 0.035 // seconds
const RELEASE_TAU = 0.22 // seconds
@@ -375,11 +388,18 @@ export class AudioEngine {
return (this.timeData[idx] - 128) / 128
}
- /** Spectrum magnitude at position 0..1 → 0..1 (log-ish frequency mapping). */
+ /**
+ * Spectrum magnitude at position 0..1 → 0..1 (log-ish frequency mapping).
+ * The field spans bin 2 (~12 Hz) to the SPEC_TOP_HZ bin (~16 kHz), so
+ * every position lands on frequencies music actually occupies -- see
+ * SPEC_TOP_HZ above for why it must not stretch to Nyquist.
+ */
getSpectrum(pos: number): number {
if (this.freqData.length === 0 || this.kind === 'idle') return 0
const p = Math.min(1, Math.max(0, pos))
- const bin = 2 + Math.floor(Math.pow(p, 1.6) * (this.freqData.length - 3))
+ const binHz = this.sampleRate / 2 / this.freqData.length
+ const hiBin = Math.max(3, Math.min(this.freqData.length - 1, Math.ceil(SPEC_TOP_HZ / binHz)))
+ const bin = 2 + Math.floor(Math.pow(p, 1.6) * (hiBin - 2))
return this.freqData[bin] / 255
}
}
\ No newline at end of file
diff --git a/src/lib/ava/embedded-presets.ts b/src/lib/ava/embedded-presets.ts
index 4a68d4f..87ff444 100755
--- a/src/lib/ava/embedded-presets.ts
+++ b/src/lib/ava/embedded-presets.ts
@@ -7,16 +7,18 @@
// for originals that rely on effects the script engine cannot run.
//
// The "Zai-Magic" folder at the END of EMBEDDED_LIBRARY is the exception to
-// "do not edit": ten presets HAND-AUTHORED for this engine (v7.00) to show
-// off beat-synced color — they use hsvr/hsvg/hsvb, beatage, megabuf particle
-// physics, getspec() and loop(). If the converter ever regenerates this
-// file, keep that section verbatim.
+// "do not edit": eleven presets HAND-AUTHORED for this engine (v7.00 + the
+// v7.12 osc-rotations addition) to show off beat-synced color and the v7.12
+// background transform -- they use hsvr/hsvg/hsvb, beatage, megabuf particle
+// physics, getspec(), loop() and the bg* framebuffer controls. If the
+// converter ever regenerates this file, keep that section verbatim.
//
// EMBEDDED_SEED_VERSION: bump whenever this library is regenerated — the
// GET /api/presets route re-seeds the root/embedded subtree when it changes
-// (v7.10: em dashes in descriptions/script comments replaced with '--').
+// (v7.10: em dashes in descriptions/script comments replaced with '--';
+// v7.12: eleventh Zai-Magic preset "osc-rotations" added).
-export const EMBEDDED_SEED_VERSION = '7.10'
+export const EMBEDDED_SEED_VERSION = '7.12'
export interface EmbeddedPreset {
folder: string
@@ -842,11 +844,13 @@ export const EMBEDDED_LIBRARY: EmbeddedPreset[] = [
},
// ==========================================================================
- // Zai-Magic — ten presets hand-authored for this engine (v7.00).
- // Every one is built around BEAT-SYNCED COLOR: the color wheel turns on
- // the kick, palettes swap per beat or per 4-beat bar, and kick envelopes
- // (beatage) drive brightness. Techniques span all three render modes,
- // megabuf particle physics, direct getspec() calls and loop() setup.
+ // Zai-Magic — eleven presets hand-authored for this engine (v7.00; the
+ // eleventh, "osc-rotations", shipped with v7.12). Every one is built around
+ // BEAT-SYNCED COLOR: the color wheel turns on the kick, palettes swap per
+ // beat or per 4-beat bar, and kick envelopes (beatage) drive brightness;
+ // osc-rotations adds the v7.12 background transform (bgrot/bgzoom) to the
+ // toolkit. Techniques span all three render modes, megabuf particle
+ // physics, direct getspec() calls and loop() setup.
// KEEP VERBATIM when regenerating this file with the converter.
// ==========================================================================
{
@@ -1314,6 +1318,46 @@ red = hsvr(hue, sat, lum + flash * lit * 0.55)
green = hsvg(hue, sat, lum + flash * lit * 0.55)
blue = hsvb(hue, sat, lum + flash * lit * 0.55)
size = 2.4 + lit * 1.7 + treb * 1.2
+`,
+ },
+ {
+ folder: "root/embedded/Zai-Magic",
+ name: "osc-rotations",
+ author: "Zai",
+ description:
+ "A stationary centered oscilloscope over a background that rotates to the music -- every beat temporarily accelerates the spin, a hard hit flips it between clockwise and counter-clockwise, and the fade to black stays slow so the colors blend. The left and right points of the line color-shift together while the center points hold their own hue. Hand-authored for OVE (v7.12 background transform + mirror-symmetric hsv color).",
+ content: `name: "osc-rotations"
+author: "Zai"
+mode: lines
+blend: additive
+persist: 0.965
+linewidth: 2.2
+
+[init]
+n = 512
+hue = 0.58
+dir = 1
+
+[frame]
+punch = exp(-beatage * 3.2)
+bgrot = dir * (0.0032 + punch * (0.014 + vol_att * 0.012))
+bgzoom = 1 - 0.0009 - punch * 0.0011
+hue = frac(hue + dt * 0.012 + beat * 0.02)
+
+[beat]
+if(above(bass, bass_att * 1.25), dir = -dir, 0)
+
+[point]
+x = i * 2 - 1
+y = v * (0.3 + vol_att * 0.8)
+d = abs(i - 0.5) * 2
+h = hue + d * 0.34
+lum = 0.5 + 0.38 * vol_att + punch * 0.22
+red = hsvr(h, 0.95, lum)
+green = hsvg(h, 0.95, lum)
+blue = hsvb(h, 0.95, lum)
+size = 2 + punch * 1.6
+alpha = 0.92
`,
},
]
diff --git a/src/lib/ava/engine.ts b/src/lib/ava/engine.ts
index 018abbd..2f9c779 100755
--- a/src/lib/ava/engine.ts
+++ b/src/lib/ava/engine.ts
@@ -16,6 +16,15 @@ import { DEFAULT_BAND_CONFIG, DEFAULT_BEAT_CONFIG } from './types'
const MAX_POINTS = 8192
const POINT_STRIDE = 8 // x, y, red, green, blue, skip, size, alpha
+/** Read a script bg* control value. Non-finite (NaN / Infinity from a bad
+ * script computation) and missing values fall back to the neutral value --
+ * a NaN must never reach a canvas transform (it poisons the matrix and every
+ * later draw becomes a silent no-op: a black screen with no error). */
+function bgNum(v: number | undefined, neutral: number): number {
+ const x = typeof v === 'number' ? v : neutral
+ return Number.isFinite(x) ? x : neutral
+}
+
export interface EngineStats {
fps: number
levels: BandLevels
@@ -54,6 +63,12 @@ export class AvaEngine {
/** ctx.filter support (older Safari lacks it → plain crossfade fallback). */
private canFilter = false
+ /** v7.12 background-feedback buffer. Same pixel size as the live canvas;
+ * recreated automatically on resize. Null until a script first asks for a
+ * background transform (zero cost for every preset that doesn't). */
+ private bgBuffer: HTMLCanvasElement | null = null
+ private bgBufCtx: CanvasRenderingContext2D | null = null
+
constructor() {
// AVS-eel compatibility hooks — the expression engine reads audio/time
// through these (getspec/getosc/gettime). Headless tests can override.
@@ -141,6 +156,60 @@ export class AvaEngine {
return this.script?.meta.name ?? '--'
}
+ /**
+ * v7.12 — background feedback transform (the AVS movement family).
+ *
+ * The whole framebuffer is moved BEFORE this frame's points are drawn:
+ * 1. snapshot the live canvas into the offscreen buffer;
+ * 2. wipe the canvas black;
+ * 3. draw the snapshot back through the frame's transform.
+ *
+ * bgrot radians per frame — + rotates the picture CLOCKWISE on screen
+ * (canvas y is down, so ctx.rotate(+θ) spins cw); - = ccw.
+ * bgx/bgy drift per frame in the -1..1 screen convention (y is UP,
+ * matching script x/y) — slide up, down, left, right.
+ * bgzoom per-frame scale — <1 draw-in (shrinks toward center),
+ * >1 blow-out (expands outward); 1 = neutral.
+ *
+ * Uncovered regions (rotated corners, slid-off edges) read black, and the
+ * persistence fade (persist header / env.persist) blends the trails —
+ * which is exactly how the classic AVS plugins made "speed accelerates to
+ * the beat, a hard hit flips the spin, fade to black stays slow" work:
+ * the rate is just a script variable driven by beatage envelopes, and a
+ * [beat] section flips the sign on a hard hit (bass vs bass_att).
+ */
+ private applyBackground(w: number, h: number, rot: number, tx: number, ty: number, zoom: number): void {
+ const canvas = this.canvas
+ const ctx = this.ctx
+ if (!canvas || !ctx) return
+ if (!this.bgBuffer || this.bgBuffer.width !== w || this.bgBuffer.height !== h) {
+ const buf = document.createElement('canvas')
+ buf.width = w
+ buf.height = h
+ this.bgBuffer = buf
+ this.bgBufCtx = buf.getContext('2d')
+ }
+ const bctx = this.bgBufCtx
+ if (!bctx || !this.bgBuffer) return
+ // 1. snapshot the live canvas ('copy' replaces the buffer wholesale)
+ bctx.globalCompositeOperation = 'copy'
+ bctx.drawImage(canvas, 0, 0)
+ // 2. wipe — the transformed snapshot must REPLACE the old content, not
+ // double-expose over it
+ ctx.globalCompositeOperation = 'source-over'
+ ctx.fillStyle = '#000'
+ ctx.fillRect(0, 0, w, h)
+ // 3. draw the snapshot back through this frame's transform. Screen y is
+ // down; script y is up — bgy > 0 moves the picture UP.
+ ctx.save()
+ ctx.imageSmoothingEnabled = true
+ ctx.translate(w / 2 + tx * (w / 2), h / 2 - ty * (h / 2))
+ if (rot !== 0) ctx.rotate(rot)
+ if (zoom !== 1) ctx.scale(zoom, zoom)
+ ctx.drawImage(this.bgBuffer, -w / 2, -h / 2)
+ ctx.restore()
+ }
+
private resize(): void {
if (!this.canvas) return
const parent = this.canvas.parentElement
@@ -227,6 +296,20 @@ export class AvaEngine {
} catch (err) {
this.scriptError = `runtime error: ${String(err)}`
}
+
+ // ---- background feedback transform (v7.12) --------------------------
+ // AVS-style whole-framebuffer movement, read AFTER [frame]/[beat] ran
+ // so this frame's values apply this frame. Set from [init] (persists
+ // forever), [frame] or [beat] (every frame / on the kick). Values live
+ // in env like any variable: a script that sets them only on beats gets
+ // on-beat jumps. Neutral readings skip the pass entirely.
+ const bgRot = Math.max(-0.6, Math.min(0.6, bgNum(env.bgrot, 0)))
+ const bgX = Math.max(-0.25, Math.min(0.25, bgNum(env.bgx, 0)))
+ const bgY = Math.max(-0.25, Math.min(0.25, bgNum(env.bgy, 0)))
+ const bgZoom = Math.max(0.5, Math.min(2, bgNum(env.bgzoom, 1)))
+ if (bgRot !== 0 || bgX !== 0 || bgY !== 0 || bgZoom !== 1) {
+ this.applyBackground(w, h, bgRot, bgX, bgY, bgZoom)
+ }
}
// ---- point evaluation ---------------------------------------------------
diff --git a/src/lib/ava/wake-lock.ts b/src/lib/ava/wake-lock.ts
new file mode 100644
index 0000000..7f8d802
--- /dev/null
+++ b/src/lib/ava/wake-lock.ts
@@ -0,0 +1,128 @@
+// OVE — Screen Wake Lock manager (v7.11).
+//
+// Mobile browsers dim and eventually turn the screen off after a few seconds
+// of inactivity — correct for reading, fatal for a music visualizer propped
+// on a table. While audio is actually playing (demo / mic / file / link —
+// anything but idle) the app holds a screen wake lock so the visuals stay
+// visible; the moment playback stops the lock is released and the phone's
+// normal inactivity timeout takes over again.
+//
+// API notes (developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API):
+// • navigator.wakeLock.request('screen') → Promise
+// • The browser SILENTLY releases the sentinel when the page becomes
+// hidden (app switch / screen lock) — so a 'visibilitychange' listener
+// re-acquires it when the visitor comes back and music is still going.
+// • request() rejects when unsupported (older iOS Safari, Firefox Android
+// pre-126), when the document is hidden, or when a platform setting
+// refuses — every rejection is swallowed: the lock is a nice-to-have,
+// never a blocker.
+// • Wake locks are free — no battery drain beyond the screen itself, and
+// the page already renders at full tilt while playing.
+
+/** Minimal structural type — lib.dom versions vary in Wake Lock coverage. */
+interface WakeLockSentinelLike {
+ readonly released: boolean
+ release: () => Promise
+ addEventListener: (type: 'release', listener: () => void) => void
+ removeEventListener: (type: 'release', listener: () => void) => void
+}
+
+interface WakeLockLike {
+ request: (type: 'screen') => Promise
+}
+
+/** The held sentinel (null = not held / browser released it). */
+let sentinel: WakeLockSentinelLike | null = null
+/** Whether a lock is currently WANTED (music playing). */
+let wantActive = false
+/** Guards against overlapping request() calls. */
+let requesting = false
+/** The sentinel's own release listener (kept so it can be detached). */
+let releaseListener: (() => void) | null = null
+
+function navigatorWakeLock(): WakeLockLike | null {
+ if (typeof navigator === 'undefined') return null
+ const nav = navigator as Navigator & { wakeLock?: unknown }
+ const wl = nav.wakeLock
+ if (!wl || typeof wl !== 'object') return null
+ const typed = wl as Partial
+ return typeof typed.request === 'function' ? (typed as WakeLockLike) : null
+}
+
+/** Drop our reference when the browser auto-releases (page hidden). */
+function onSentinelRelease(): void {
+ sentinel = null
+ releaseListener = null
+}
+
+async function acquire(): Promise {
+ if (sentinel !== null || requesting) return
+ const wl = navigatorWakeLock()
+ if (!wl) return
+ requesting = true
+ try {
+ const s = await wl.request('screen')
+ // state may have flipped to "stopped" while the promise was in flight
+ if (!wantActive) {
+ try {
+ await s.release()
+ } catch {
+ /* already gone */
+ }
+ return
+ }
+ sentinel = s
+ releaseListener = onSentinelRelease
+ s.addEventListener('release', releaseListener)
+ } catch {
+ /* unsupported / hidden / refused — degrade silently */
+ } finally {
+ requesting = false
+ }
+}
+
+function dropSentinel(): void {
+ const s = sentinel
+ sentinel = null
+ if (s) {
+ if (releaseListener) s.removeEventListener('release', releaseListener)
+ releaseListener = null
+ s.release().catch(() => {
+ /* already released */
+ })
+ }
+}
+
+/**
+ * Hold or release the screen wake lock according to playback. Called with
+ * `true` whenever a source starts, `false` whenever the engine goes idle.
+ */
+export function syncWakeLock(active: boolean): void {
+ wantActive = active
+ if (!active) {
+ dropSentinel()
+ return
+ }
+ if (typeof document !== 'undefined' && document.visibilityState === 'visible') {
+ void acquire()
+ }
+ // hidden right now (rare — playback needs a gesture, but a queued playlist
+ // could land here): the visibilitychange listener below acquires on return.
+}
+
+/** Explicit teardown (hook unmount / pagehide). */
+export function releaseWakeLock(): void {
+ syncWakeLock(false)
+}
+
+/**
+ * Re-acquire the lock when the page becomes visible again while music is
+ * still playing (the browser always drops it on hide). Returns a cleanup.
+ */
+export function watchWakeLockVisibility(): () => void {
+ const onVisibility = () => {
+ if (document.visibilityState === 'visible' && wantActive) void acquire()
+ }
+ document.addEventListener('visibilitychange', onVisibility)
+ return () => document.removeEventListener('visibilitychange', onVisibility)
+}
diff --git a/src/lib/release-notes.ts b/src/lib/release-notes.ts
index 5cf7d45..4bd35b4 100755
--- a/src/lib/release-notes.ts
+++ b/src/lib/release-notes.ts
@@ -39,6 +39,34 @@ export interface ReleaseNote {
}
export const RELEASE_NOTES: ReleaseNote[] = [
+ {
+ version: '7.13',
+ name: 'One build, every feature',
+ changes: [
+ {
+ kind: 'improved',
+ text: 'The two development lines are merged into one build. Everything from the parallel line -- the unfrozen spectrum edge, the screen wake lock, the respectful Demo button, one-click saving, the background movement family and the "osc-rotations" preset -- now lives alongside the community OVE-Directory in this release.',
+ },
+ {
+ kind: 'improved',
+ text: 'The Script tab toolbar carries the one-click Save and "Upload to OVE-Directory" side by side, and the release list below now reads as one timeline (the parallel line\u2019s wake-lock release is folded in as 7.10.2).',
+ },
+ ],
+ },
+ {
+ version: '7.12',
+ name: 'Background movement (AVS-style) + osc-rotations',
+ changes: [
+ {
+ kind: 'added',
+ text: 'Scripts can move the whole picture now: bgrot spins the background clockwise or counter-clockwise, bgx / bgy slide it up, down, left or right, and bgzoom draws it in or blows it out -- the movement family the classic Winamp AVS plugins were built on. The speed can ride the music: accelerate on every beat, flip the spin direction on a hard hit, and let a slow fade to black blend the colors. All four variables are documented in the script editor\'s help panel.',
+ },
+ {
+ kind: 'added',
+ text: 'I added an eleventh Zai-Magic preset: "osc-rotations" -- a stationary centered oscilloscope whose left and right points color-shift together while the center points hold their own hue, over a background that rotates clockwise and counter-clockwise to the music and temporarily accelerates with the beat. Hard hits flip the spin; the fade stays slow so the colors blend.',
+ },
+ ],
+ },
{
version: '7.11',
name: 'The community OVE-Directory',
@@ -61,6 +89,34 @@ export const RELEASE_NOTES: ReleaseNote[] = [
},
],
},
+ {
+ version: '7.10.2',
+ name: 'Wake lock, a respectful Demo, one-click save',
+ changes: [
+ {
+ kind: 'added',
+ text: 'The screen stays on while music plays: on phones and tablets the site now holds a screen wake lock for as long as any audio source is live, then lets go the moment playback stops so the device\'s normal screen timeout takes over again.',
+ },
+ {
+ kind: 'improved',
+ text: 'The Demo Track button stopped hijacking the preset selection. When a preset is already loaded (tree double-click, Load, a copied preset) or the editor holds custom script material, Demo just plays the track and leaves everything untouched -- the Zai-Magic Sequence showcase now only starts for a visit that never picked or typed anything, so a custom script can be built and debugged against the demo track without fear of it being replaced.',
+ },
+ {
+ kind: 'improved',
+ text: 'One-click saving in the Script tab: a valid script shows the Save button even with no save target picked, and one click stores it under "user created", auto-named from the script\'s name header (a hash import of "Spectrum Bars:[▼…]" saves as "Spectrum Bars"). Nameless scripts land as "Empty Name", collisions as "(1)", "(2)"… -- and in those cases a "Give This Preset a Name" dialog opens immediately so the just-saved preset can be renamed on the spot.',
+ },
+ ],
+ },
+ {
+ version: '7.10.1',
+ name: 'Spectrum edge unfrozen',
+ changes: [
+ {
+ kind: 'fixed',
+ text: 'Spectrum visuals used to sit dead along the right edge: the bar field stretched all the way to the top of the FFT (~24 kHz), where music carries almost nothing and MP3/YouTube audio is already cut off, so the rightmost bars read permanent silence no matter how loud the track got. I capped the field at 16 kHz -- the range where music actually lives -- so every bar has something to react to.',
+ },
+ ],
+ },
{
version: '7.10',
name: 'Ten demo tracks',
diff --git a/src/lib/store.ts b/src/lib/store.ts
index 0a2a7a7..3c9634d 100755
--- a/src/lib/store.ts
+++ b/src/lib/store.ts
@@ -187,6 +187,14 @@ interface AvaState {
editTarget: EditTarget | null
/** Preset currently running on the engine (tree indicator). */
activePresetId: string | null
+ /** v7.11 — true once THIS visit made an explicit choice: loaded a preset
+ * (tree Load / double-click / copy-to-my-presets / created one), brought
+ * the visitor's own script material into the editor (typed / pasted /
+ * hash-imported / file-loaded), saved a default startup script, or runs
+ * their own mix mode. While false the Demo button may still run the
+ * welcome showcase; once true, Demo never touches the selection.
+ * Deliberately NOT persisted — every visit starts fresh. */
+ manualChoice: boolean
/** Timed preset mixing (club mode): Random (no repeats) or, v7.4, Sequence
* (in-order cycling with wrap-around). The two are mutually exclusive —
* ticking one unticks the other; both share one delay + folder scope and
@@ -219,6 +227,8 @@ interface AvaState {
selectPreset: (id: string | null) => void
setEditTarget: (t: EditTarget | null) => void
setActivePresetId: (id: string | null) => void
+ /** v7.11 — flag this visitor as having made their own choice (see field). */
+ markManualChoice: () => void
setRandomize: (on: boolean) => void
/** v7.4: Sequence mode — in-order cycling; mutually exclusive with Random. */
setSequenceMode: (on: boolean) => void
@@ -371,6 +381,7 @@ export const useAvaStore = create()((set, get) => ({
expandedFolders: {},
editTarget: null,
activePresetId: null,
+ manualChoice: false,
randomize: false,
sequenceMode: false,
randomIntervalSec: 10,
@@ -481,6 +492,8 @@ export const useAvaStore = create()((set, get) => ({
setActivePresetId: (id) => set({ activePresetId: id }),
+ markManualChoice: () => set({ manualChoice: true }),
+
setRandomize: (on) => {
// v7.4 mutual exclusion: ticking Random unticks Sequence (turning one
// OFF never lights the other — the visitor's explicit choice)
diff --git a/src/lib/version.ts b/src/lib/version.ts
index 8e95cb3..948df4f 100755
--- a/src/lib/version.ts
+++ b/src/lib/version.ts
@@ -12,6 +12,53 @@
// warns in the console when the two drift apart.
//
// History:
+// 7.13 — one build, every feature: the parallel development lines are
+// merged. This tree now carries BOTH the community OVE-Directory
+// (v7.11 here) AND the parallel line's additions — the spectrum
+// 16 kHz cap (its 7.10.1), the screen wake lock + respectful
+// Demo button + one-click Save (its 7.11, folded into this
+// history as 7.10.2 to keep one clean timeline), and the
+// AVS-style background movement family (bgrot / bgx / bgy /
+// bgzoom) with the eleventh Zai-Magic preset "osc-rotations"
+// (its 7.12). Git three-way merge against the shared v7.10
+// ancestor (identical tree hashes verified on both sides); the
+// only hand-merged surface was the Script tab toolbar, which
+// now carries the one-click Save and "Upload to OVE-Directory"
+// side by side, and the two changelogs (here + release-notes).
+// 7.12 — background movement + osc-rotations. Scripts can now move the
+// whole framebuffer every frame, the way the classic Winamp AVS
+// plugins did: bgrot (radians/frame, + clockwise on screen),
+// bgx / bgy (per-frame drift in the -1..1 screen convention, y
+// up), bgzoom (< 1 draw-in, > 1 blow-out) -- read after [frame]/
+// [beat] so this frame's values apply this frame. The engine
+// snapshots the canvas into an offscreen buffer, wipes, and
+// draws the snapshot back through the transform, so the
+// persistence fade blends the moving trails: "speed accelerates
+// to the beat, a hard hit flips the spin, fade to black stays
+// slow" is pure script (a beatage envelope drives the rate; a
+// [beat] section flips a dir variable on hard hits via bass vs
+// bass_att). Neutral readings (0/0/0/1) skip the pass entirely
+// (zero cost for every preset that doesn't move); non-finite
+// values fall back to neutral (a NaN in a canvas transform
+// matrix would black the screen with no error); per-frame
+// clamps: rot ±0.6 rad, drift ±0.25, zoom 0.5..2. New 11th
+// Zai-Magic preset "osc-rotations" (user-requested): a
+// stationary centered oscilloscope (time-domain v) whose color
+// is mirror-symmetric -- points equidistant left/right of
+// center shift together, the center points hold their own hue
+// -- over a background rotating cw/ccw to the music that
+// temporarily accelerates on the beat; hard hits
+// (bass > 1.25 × bass_att) flip the spin; a whisper of draw-in
+// (bgzoom ~0.999) spirals the trails inward; persist 0.965
+// keeps the fade slow so colors blend. Help panel: bg* variable
+// rows + three new example formulas. EMBEDDED_SEED_VERSION
+// 7.10 -> 7.12 (library re-seed adds the preset; the share
+// codec's dictionary stays frozen per its compatibility
+// contract -- bg* identifiers ride the pair packing like any
+// custom variable). Headless validation of the preset's
+// compiled programs: quiet 0.004 rad/frame, hard beat 0.026
+// (6.5×) + direction flip, soft beat no flip, mirrored points
+// share hue / center distinct
// 7.11 — the community OVE-Directory. A sixth tab (Directory) lists every
// script published to the community directory — a separate VPS at
// s3374dir.openvisengine.com, reached ONLY through this server's
@@ -32,6 +79,47 @@
// "(1) (2) (3)…" on collision) and offers "Open in editor".
// Duplicate uploads resolve as success (409 DUPLICATE_HASH carries
// the existing row); duplicate votes show the stored aggregates.
+// 7.10.2 — wake lock, a respectful Demo button, one-click save. While any
+// audio source is live the site holds a Screen Wake Lock so phone
+// and tablet screens stay on (released the moment the engine goes
+// idle so the normal inactivity dimming returns; browsers silently
+// drop the sentinel on hide, so a visibilitychange watcher
+// re-acquires it on return while music is still going;
+// unsupported browsers degrade silently). The Demo Track button no
+// longer force-engages the Sequence checkbox: a visitor who loaded
+// a preset (tree double-click / Load / Copy to my presets / created
+// one), brought their own script material into the editor (typed,
+// pasted, Hash⇢Script, file), saved their own default startup
+// script, or runs their own Random/Sequence mix keeps their
+// selection -- Demo just plays the track (the showcase's tree
+// re-scope, 20 s interval and retire-on-end all skip too; a new
+// per-visit manualChoice flag + a boot-script snapshot decide, and
+// ensureDefaultPreset gained the same guard so mic/file/link never
+// clobber a custom draft either; the showcase now only starts for
+// a visit that never picked or typed anything). Script tab: a valid
+// script shows the Save button with NO save target needed -- one
+// click creates a preset under "user created", auto-named from the
+// script's name: header (a hash import of "Spectrum Bars:[▼…]"
+// saves as "Spectrum Bars"), "Empty Name" when nameless, " (1)"/
+// " (2)"… suffixes on conflicts, and whenever the name had to be
+// invented a "Give This Preset a Name" dialog opens to rename the
+// just-saved preset in one step (the preset is always saved FIRST,
+// renamed second, so nothing is ever lost to a naming problem).
+// Hash⇢Script imports clear the stale save target (the editor now
+// holds foreign content) and pass the decoded label through as the
+// save-name fallback when the script has no name: header
+// 7.10.1 — the frozen right edge of spectrum visuals. getspec()/sv mapped
+// the bar field across the whole FFT (bin 2 ... Nyquist, ~12 Hz to
+// 24 kHz), but music carries almost nothing above ~16 kHz and every
+// lossy source low-passes even lower (192 kbps MP3 at ~18.6 kHz,
+// YouTube AAC at ~15.5 kHz; measured across the demo collection:
+// 0.000% of total magnitude above 18.6 kHz), so the rightmost ~20%
+// of every spectrum visual read a permanent zero -- bars that never
+// moved. getSpectrum() now tops the field out at 16 kHz
+// (SPEC_TOP_HZ, matching the spectral-flux cap in getBands()), so
+// the right edge lands on the 12-16 kHz "air" band where cymbals,
+// hats and synth sparkle live. rust-agent's spectrum_at() mirrors
+// the change, and the help panel's sv entry now states the span.
// 7.10 — ten demo tracks + first-person changelog + declutter. The demo
// source is now a COLLECTION: ten mastered tracks in
// public/demo-tracks/ (demo-01.mp3 … demo-10.mp3, filename order
@@ -319,4 +407,4 @@
export const APP_NAME = 'Open Visualization Engine'
export const APP_SHORT = 'OVE'
-export const APP_VERSION = '7.11'
+export const APP_VERSION = '7.13'
diff --git a/tool-results/osc12-1-preset-live.png b/tool-results/osc12-1-preset-live.png
new file mode 100644
index 0000000..8011fc1
Binary files /dev/null and b/tool-results/osc12-1-preset-live.png differ
diff --git a/tool-results/osc12-2-rot-test.png b/tool-results/osc12-2-rot-test.png
new file mode 100644
index 0000000..dfcfcc4
Binary files /dev/null and b/tool-results/osc12-2-rot-test.png differ
diff --git a/tool-results/osc12-3-final-live.png b/tool-results/osc12-3-final-live.png
new file mode 100644
index 0000000..5bc9342
Binary files /dev/null and b/tool-results/osc12-3-final-live.png differ
diff --git a/tool-results/osc12-4-mobile.png b/tool-results/osc12-4-mobile.png
new file mode 100644
index 0000000..3f48eeb
Binary files /dev/null and b/tool-results/osc12-4-mobile.png differ
diff --git a/tool-results/read_1793817131261_fb699e6a294f.txt b/tool-results/read_1793817131261_fb699e6a294f.txt
new file mode 100644
index 0000000..6f34d74
--- /dev/null
+++ b/tool-results/read_1793817131261_fb699e6a294f.txt
@@ -0,0 +1,1254 @@
+ 1→// OVE — Script ⇢ Hash share codec (v7.8 — scheme ▼, "big-base packing").
+ 2→//
+ 3→// Turns a script into ONE short shareable Unicode string and back, exactly:
+ 4→//
+ 5→// Bass Bloom:[▼䷀…一…CJK/Hangul digits…㘎〠⼁⼃㘚]
+ 6→// └─ label │└──────────────┘└─┬┘└ check digits
+ 7→// scheme ▼ 〠 = header separator
+ 8→//
+ 9→// WHY IT'S SHORT — the compression ladder (each layer feeds the next):
+ 10→//
+ 11→// 1. DICTIONARY — ~300 built-in words → 1 symbol each: every function
+ 12→// (sin(, cos(, if(, … WITH their open paren), every audio variable
+ 13→// (bass, mid, treb, vol_att, beatage, …), every header key and value,
+ 14→// statement openers ("x = " ⇦ "green = " ⇩), the AVS idioms
+ 15→// ("hue = hue + dt * ", "t = t + dt * ", " * 0.", ") * ", "\n\n[point]\n"),
+ 16→// ~70 number literals (6.28318, 3.14159, 0.55, 0.25, …) and the
+ 17→// user-named core: ♤ line return, ♡ beats, ♠ beatage, ♦ space,
+ 18→// ♩♪♫♬ the four sections.
+ 19→//
+ 20→// 2. BIG-BASE PACKING — the clever part. The dictionary output (a string
+ 21→// over ~40–120 distinct symbols) is read as ONE BIG NUMBER in base
+ 22→// |alphabet| and rewritten in base 32,164 — one digit per character,
+ 23→// the digits drawn from the CJK Unified (20,992) + Hangul (11,172)
+ 24→// blocks. That carries ~15 bits per character where base-62 (the
+ 25→// classic "1400 → Ma" trick) carries 5.95 — so "1400" costs ~0.85 of
+ 26→// a character, and a whole preset rides at roughly a THIRD of its
+ 27→// source length. The decoder gets the alphabet back from a bitmap
+ 28→// header (the dictionary is built-in, so "which symbols are used" is
+ 29→// 22 bitmap digits, not 80 literal ones).
+ 30→//
+ 31→// 3. PAIR PACKING (fallback) — scripts too short for the header to pay
+ 32→// off keep the v7.7 body instead: dictionary + two leftover
+ 33→// characters folded into one CJK code point (the classic layer —
+ 34→// it reads nicer for small scripts, and the encoder simply picks
+ 35→// whichever body is shorter).
+ 36→//
+ 37→// Guarantees (unchanged from v7.7):
+ 38→// • LOSSLESS — decode(encode(s)) === s for ANY text (comments, weird
+ 39→// whitespace, even scripts that don't compile). Anything the dictionary
+ 40→// doesn't cover passes through untouched, and every reserved symbol
+ 41→// that legitimately appears in a script is escaped ("\♠" → literal ♠).
+ 42→// • COPY-SAFE — no raw whitespace or control characters anywhere in the
+ 43→// string (folded to symbols / control pictures), so chat apps and
+ 44→// social sites can't collapse it. Emoji-variation selectors and
+ 45→// zero-width chars that platforms inject while copying are stripped on
+ 46→// decode (and escaped on encode, so a script that really contains them
+ 47→// still round-trips).
+ 48→// • SELF-CHECKING — the last 8 chars are check digits (①–⑯ as hex) over
+ 49→// an FNV-1a digest of the script, so a string that was cut off by a
+ 50→// character limit or altered in transit is rejected honestly instead
+ 51→// of decoding into garbage.
+ 52→//
+ 53→// The "Name:" prefix is cosmetic (the script's own `name:` header rides
+ 54→// inside the body untouched) — it just makes the string self-describing in
+ 55→// a social feed.
+ 56→//
+ 57→// ▲ strings from v7.7 still decode (their tables are kept below, decode
+ 58→// only). ▼ is everything since.
+ 59→
+ 60→// ── scheme markers ─────────────────────────────────────────────────────────
+ 61→
+ 62→/** v7.7 strings (dictionary + pair packing). Decode-only. */
+ 63→const SCHEME_V1 = '▲'
+ 64→/** v7.8+ strings (dictionary + big-base or pair, whichever is shorter). */
+ 65→const SCHEME_V2 = '▼'
+ 66→
+ 67→/** Escape char — prefixes any reserved symbol that appears literally. */
+ 68→const ESCAPE = '\\'
+ 69→
+ 70→/**
+ 71→ * Header separator (U+3020 POSTAL MARK FACE). Splits the payload digits
+ 72→ * from the bitmap header. Reserved, so it only ever appears in a body as
+ 73→ * an escaped pair — the decoder can treat the FIRST bare 〠 as structural.
+ 74→ */
+ 75→const SEP = '〠'
+ 76→
+ 77→interface DictEntry {
+ 78→ key: string
+ 79→ symbol: string
+ 80→}
+ 81→
+ 82→// ── the dictionary ─────────────────────────────────────────────────────────
+ 83→
+ 84→/**
+ 85→ * The substitution dictionary. Longest key wins at every position, so
+ 86→ * "smoothstep(" resolves before "smoothstep", "beatage" before "beat" and
+ 87→ * "hue = hue + dt * " before "hue = ". Symbols are single BMP code points,
+ 88→ * none of them ASCII.
+ 89→ *
+ 90→ * COMPATIBILITY CONTRACT: the symbols of this table (plus the noise
+ 91→ * symbols below) form the frozen BITMAP_LIST that ▼ headers are built
+ 92→ * from. Adding entries to DICT changes BITMAP_LIST and silently breaks
+ 93→ * every ▼ string encoded by an older build — after v7.8 ships, new
+ 94→ * dictionary entries require a new scheme marker (▽), not an edit here.
+ 95→ */
+ 96→const DICT: DictEntry[] = [
+ 97→ // ── user-named core references ─────────────────────────────────────────
+ 98→ { key: '\n', symbol: '♤' }, // line return
+ 99→ { key: ' ', symbol: '♦' }, // space
+ 100→ { key: 'beats', symbol: '♡' },
+ 101→ { key: 'beatage', symbol: '♠' },
+ 102→ { key: '\t', symbol: '♣' }, // tab
+ 103→ // ── line ends (auto-ported presets are full of these) ──────────────────
+ 104→ { key: '\r\n', symbol: '⼀' },
+ 105→ { key: ';\r\n', symbol: '⼁' },
+ 106→ { key: ';\n', symbol: '⼂' },
+ 107→ { key: '; ', symbol: '⼃' },
+ 108→ // ── whitespace runs ────────────────────────────────────────────────────
+ 109→ { key: ' ', symbol: '▣' }, // 4-space indent
+ 110→ { key: ' ', symbol: '·' }, // 2-space indent
+ 111→ { key: '\r', symbol: '⏎' }, // lone carriage return
+ 112→ // ── section headers (music notes) ──────────────────────────────────────
+ 113→ { key: '[init]', symbol: '♩' },
+ 114→ { key: '[frame]', symbol: '♪' },
+ 115→ { key: '[beat]', symbol: '♫' },
+ 116→ { key: '[point]', symbol: '♬' },
+ 117→ // ── blank line + section (the generated-preset skeleton) ───────────────
+ 118→ { key: '\n\n', symbol: '⽞' },
+ 119→ { key: '\n\n[init]\n', symbol: '⽟' },
+ 120→ { key: '\n\n[frame]\n', symbol: '⽠' },
+ 121→ { key: '\n\n[beat]\n', symbol: '⽡' },
+ 122→ { key: '\n\n[point]\n', symbol: '⽢' },
+ 123→ // ── comments ───────────────────────────────────────────────────────────
+ 124→ { key: '# ', symbol: '♯' },
+ 125→ { key: '#', symbol: '♭' },
+ 126→ { key: '//', symbol: '♮' },
+ 127→ // ── header keys ────────────────────────────────────────────────────────
+ 128→ { key: 'description', symbol: '♖' },
+ 129→ { key: 'linewidth: ', symbol: '⼅' },
+ 130→ { key: 'blend: additive', symbol: '⼆' },
+ 131→ { key: 'mode: lines', symbol: '⼇' },
+ 132→ { key: 'mode: dots', symbol: '⼈' },
+ 133→ { key: 'linewidth', symbol: '♚' },
+ 134→ { key: 'persist', symbol: '♙' },
+ 135→ { key: 'author', symbol: '♕' },
+ 136→ { key: 'ysign', symbol: '♛' },
+ 137→ { key: 'blend', symbol: '♘' },
+ 138→ { key: 'name', symbol: '♔' },
+ 139→ { key: 'mode', symbol: '♗' },
+ 140→ { key: ': ', symbol: '⼄' }, // every other header's colon
+ 141→ // ── header values (zodiac) ─────────────────────────────────────────────
+ 142→ { key: 'additive', symbol: '♋' },
+ 143→ { key: 'lines', symbol: '♈' },
+ 144→ { key: 'dots', symbol: '♉' },
+ 145→ { key: 'solid', symbol: '♊' },
+ 146→ { key: 'alpha', symbol: '♌' },
+ 147→ // ── built-in variables (the engine's full injected set) ────────────────
+ 148→ { key: 'bass_att', symbol: '♎' },
+ 149→ { key: 'mid_att', symbol: '♏' },
+ 150→ { key: 'treb_att', symbol: '♐' },
+ 151→ { key: 'vol_att', symbol: '⼉' },
+ 152→ { key: 'beatphase', symbol: '⼊' },
+ 153→ { key: 'beatconf', symbol: '⼋' },
+ 154→ { key: 'bass', symbol: '♑' },
+ 155→ { key: 'treb', symbol: '♓' },
+ 156→ { key: 'frame', symbol: '⼌' },
+ 157→ { key: 'time', symbol: '⼍' },
+ 158→ { key: 'mid', symbol: '♒' },
+ 159→ { key: 'vol', symbol: '☽' },
+ 160→ { key: 'beat', symbol: '◉' },
+ 161→ { key: 'red', symbol: '☾' },
+ 162→ { key: 'blue', symbol: '⚑' },
+ 163→ { key: 'skip', symbol: '⚐' },
+ 164→ { key: 'green', symbol: '☿' },
+ 165→ { key: 'aspect', symbol: '⚶' },
+ 166→ { key: 'chroma', symbol: '⼏' },
+ 167→ { key: 'dt', symbol: '◐' },
+ 168→ { key: 'pi', symbol: '◑' },
+ 169→ { key: 'tau', symbol: '◒' },
+ 170→ { key: 'bpm', symbol: '⼎' },
+ 171→ { key: 'hue', symbol: '⋊' },
+ 172→ { key: 'rad', symbol: '⋅' },
+ 173→ { key: 'flash', symbol: '∘' },
+ 174→ { key: 'sv', symbol: '⼐' }, // per-point spectrum level
+ 175→ // ── functions WITH their open paren (a call costs one fewer symbol) ────
+ 176→ { key: 'smoothstep(', symbol: '⼑' },
+ 177→ { key: 'gmegabuf(', symbol: '⼒' },
+ 178→ { key: 'getkbmouse(', symbol: '⼓' },
+ 179→ { key: 'megabuf(', symbol: '⼔' },
+ 180→ { key: 'getspec(', symbol: '⼕' },
+ 181→ { key: 'getosc(', symbol: '⼖' },
+ 182→ { key: 'gettime(', symbol: '⼗' },
+ 183→ { key: 'invsqrt(', symbol: '⼘' },
+ 184→ { key: 'sigmoid(', symbol: '⼙' },
+ 185→ { key: 'atan2(', symbol: '⼚' },
+ 186→ { key: 'inrange(', symbol: '⼛' },
+ 187→ { key: 'log10(', symbol: '⼜' },
+ 188→ { key: 'floor(', symbol: '⼝' },
+ 189→ { key: 'above(', symbol: '⼞' },
+ 190→ { key: 'below(', symbol: '⼟' },
+ 191→ { key: 'equal(', symbol: '⼠' },
+ 192→ { key: 'round(', symbol: '⼡' },
+ 193→ { key: 'clamp(', symbol: '⼢' },
+ 194→ { key: 'lerp(', symbol: '⼣' },
+ 195→ { key: 'asin(', symbol: '⼤' },
+ 196→ { key: 'acos(', symbol: '⼥' },
+ 197→ { key: 'atan(', symbol: '⼦' },
+ 198→ { key: 'sign(', symbol: '⼧' },
+ 199→ { key: 'band(', symbol: '⼨' },
+ 200→ { key: 'bnot(', symbol: '⼩' },
+ 201→ { key: 'hsvr(', symbol: '⼪' },
+ 202→ { key: 'hsvg(', symbol: '⼫' },
+ 203→ { key: 'hsvb(', symbol: '⼬' },
+ 204→ { key: 'ceil(', symbol: '⼭' },
+ 205→ { key: 'frac(', symbol: '⼮' },
+ 206→ { key: 'pow(', symbol: '⼯' },
+ 207→ { key: 'exp(', symbol: '⼰' },
+ 208→ { key: 'log(', symbol: '⼱' },
+ 209→ { key: 'int(', symbol: '⼲' },
+ 210→ { key: 'min(', symbol: '⼳' },
+ 211→ { key: 'max(', symbol: '⼴' },
+ 212→ { key: 'abs(', symbol: '⼵' },
+ 213→ { key: 'sin(', symbol: '⼶' },
+ 214→ { key: 'cos(', symbol: '⼷' },
+ 215→ { key: 'tan(', symbol: '⼸' },
+ 216→ { key: 'if(', symbol: '⼹' },
+ 217→ { key: 'rnd(', symbol: '⼺' },
+ 218→ { key: 'rand(', symbol: '⼻' },
+ 219→ { key: 'loop(', symbol: '⼼' },
+ 220→ // ── functions without paren (for the times they're not calls) ──────────
+ 221→ { key: 'smoothstep', symbol: '⌒' },
+ 222→ { key: 'gmegabuf', symbol: '✥' },
+ 223→ { key: 'getkbmouse', symbol: '◶' },
+ 224→ { key: 'atan2', symbol: '⌗' },
+ 225→ { key: 'megabuf', symbol: '✤' },
+ 226→ { key: 'getspec', symbol: '▤' },
+ 227→ { key: 'getosc', symbol: '▥' },
+ 228→ { key: 'gettime', symbol: '◷' },
+ 229→ { key: 'log10', symbol: '㏑' },
+ 230→ { key: 'invsqrt', symbol: '√' },
+ 231→ { key: 'inrange', symbol: '⋔' },
+ 232→ { key: 'sigmoid', symbol: '∿' },
+ 233→ { key: 'floor', symbol: '⌊' },
+ 234→ { key: 'above', symbol: '≻' },
+ 235→ { key: 'below', symbol: '≺' },
+ 236→ { key: 'asin', symbol: '⌠' },
+ 237→ { key: 'acos', symbol: '⌡' },
+ 238→ { key: 'atan', symbol: '⌐' },
+ 239→ { key: 'ceil', symbol: '⌈' },
+ 240→ { key: 'sqr', symbol: '□' },
+ 241→ { key: 'sqrt', symbol: '∛' },
+ 242→ { key: 'pow', symbol: '⌃' },
+ 243→ { key: 'exp', symbol: 'ℯ' },
+ 244→ { key: 'log', symbol: '㏒' },
+ 245→ { key: 'round', symbol: '◎' },
+ 246→ { key: 'int', symbol: '⊺' },
+ 247→ { key: 'frac', symbol: '∵' },
+ 248→ { key: 'sign', symbol: '±' },
+ 249→ { key: 'bnot', symbol: '¬' },
+ 250→ { key: 'band', symbol: '∧' },
+ 251→ { key: 'bor', symbol: '∨' },
+ 252→ { key: 'equal', symbol: '≟' },
+ 253→ { key: 'lerp', symbol: '↝' },
+ 254→ { key: 'clamp', symbol: '⊜' },
+ 255→ { key: 'rand', symbol: '⚄' },
+ 256→ { key: 'rnd', symbol: '⚅' },
+ 257→ { key: 'sin', symbol: '◠' },
+ 258→ { key: 'cos', symbol: '◡' },
+ 259→ { key: 'tan', symbol: '◮' },
+ 260→ { key: 'abs', symbol: '¦' },
+ 261→ { key: 'min', symbol: '≪' },
+ 262→ { key: 'max', symbol: '≫' },
+ 263→ { key: 'if', symbol: '∴' },
+ 264→ { key: 'loop', symbol: '↻' },
+ 265→ // ── near-universal user variables (mined from the preset library) ──────
+ 266→ { key: 'rings', symbol: '⼽' },
+ 267→ { key: 'spokes', symbol: '⼾' },
+ 268→ { key: 'travel', symbol: '⼿' },
+ 269→ { key: 'bars', symbol: '⽀' },
+ 270→ { key: 'segs', symbol: '⽁' },
+ 271→ { key: 'ring', symbol: '⽂' },
+ 272→ { key: 'spoke', symbol: '⽃' },
+ 273→ { key: 'fade', symbol: '⽄' },
+ 274→ { key: 'size', symbol: '⽅' },
+ 275→ { key: 'rot', symbol: '⽆' },
+ 276→ { key: 'seg', symbol: '⽇' },
+ 277→ { key: 'col', symbol: '⽈' },
+ 278→ { key: 'lum', symbol: '⽉' },
+ 279→ { key: 'amp', symbol: '⽊' },
+ 280→ { key: 'spin', symbol: '⽋' },
+ 281→ { key: 'drift', symbol: '⽌' },
+ 282→ { key: 'orbit', symbol: '⽍' },
+ 283→ { key: 'pulse', symbol: '⽎' },
+ 284→ { key: 'glow', symbol: '⽏' },
+ 285→ { key: 'phase', symbol: '⽐' },
+ 286→ { key: 'wobble', symbol: '⽑' },
+ 287→ { key: 'angle', symbol: '⽒' },
+ 288→ // ── structural idioms (mined — the top repeated shapes) ────────────────
+ 289→ { key: ' * 0.', symbol: '⽓' },
+ 290→ { key: ' * (0.', symbol: '⽔' },
+ 291→ { key: '(0.', symbol: '⽕' },
+ 292→ { key: ') * ', symbol: '⽖' },
+ 293→ { key: 't * ', symbol: '⽗' },
+ 294→ { key: '5 + ', symbol: '⽘' },
+ 295→ { key: ' + dt * ', symbol: '⽙' },
+ 296→ { key: '(a)', symbol: '⽚' },
+ 297→ { key: 'hue = hue + dt * ', symbol: '⽛' },
+ 298→ { key: 't = t + dt * ', symbol: '⽜' },
+ 299→ { key: ' / aspect', symbol: '⽝' },
+ 300→ // ── number literals (mined from the library) ───────────────────────────
+ 301→ { key: '6.28318', symbol: '⌇' }, // 2π, written out in most scripts
+ 302→ { key: '3.14159', symbol: '⽣' },
+ 303→ { key: '3.1415', symbol: '⽤' },
+ 304→ { key: '0.55', symbol: '§' },
+ 305→ { key: '0.45', symbol: '¶' },
+ 306→ { key: '0.35', symbol: '※' },
+ 307→ { key: '0.25', symbol: '•' },
+ 308→ { key: '0.85', symbol: '⊢' },
+ 309→ { key: '0.75', symbol: '⽥' },
+ 310→ { key: '0.65', symbol: '⽦' },
+ 311→ { key: '0.95', symbol: '⽧' },
+ 312→ { key: '0.15', symbol: '⽨' },
+ 313→ { key: '0.12', symbol: '⽩' },
+ 314→ { key: '0.22', symbol: '⽪' },
+ 315→ { key: '0.42', symbol: '⽫' },
+ 316→ { key: '0.05', symbol: '⽬' },
+ 317→ { key: '0.06', symbol: '⽭' },
+ 318→ { key: '0.04', symbol: '⽮' },
+ 319→ { key: '0.07', symbol: '⽯' },
+ 320→ { key: '0.08', symbol: '⽰' },
+ 321→ { key: '0.96', symbol: '⽱' },
+ 322→ { key: '0.97', symbol: '⽲' },
+ 323→ { key: '2.09', symbol: '⽳' },
+ 324→ { key: '4.19', symbol: '⽴' },
+ 325→ { key: '0.5', symbol: '†' },
+ 326→ { key: '0.3', symbol: '‡' },
+ 327→ { key: '0.4', symbol: '⊤' },
+ 328→ { key: '0.7', symbol: '⊥' },
+ 329→ { key: '0.6', symbol: '⊣' },
+ 330→ { key: '0.9', symbol: '⽵' },
+ 331→ { key: '0.8', symbol: '⽶' },
+ 332→ { key: '0.2', symbol: '⽷' },
+ 333→ { key: '0.1', symbol: '⽸' },
+ 334→ { key: '1.3', symbol: '⋈' },
+ 335→ { key: '1.2', symbol: '⋉' },
+ 336→ { key: '1.0', symbol: '⽹' },
+ 337→ { key: '100', symbol: '⽺' },
+ 338→ { key: '2.5', symbol: '⽻' },
+ 339→ { key: '0.11', symbol: '⽼' },
+ 340→ { key: '0.18', symbol: '⽽' },
+ 341→ { key: '0.92', symbol: '⽾' },
+ 342→ { key: '0.99', symbol: '⽿' },
+ 343→ { key: '1.5', symbol: '⾀' },
+ 344→ { key: '0.98', symbol: '⾁' },
+ 345→ { key: '0.13', symbol: '⾂' },
+ 346→ { key: '0.14', symbol: '⾃' },
+ 347→ { key: '0.16', symbol: '⾄' },
+ 348→ { key: '0.17', symbol: '⾅' },
+ 349→ { key: '0.19', symbol: '⾆' },
+ 350→ { key: '0.21', symbol: '⾇' },
+ 351→ { key: '0.28', symbol: '⾈' },
+ 352→ { key: '0.31', symbol: '⾉' },
+ 353→ { key: '0.32', symbol: '⾊' },
+ 354→ { key: '0.33', symbol: '⾋' },
+ 355→ { key: '0.38', symbol: '⾌' },
+ 356→ { key: '0.44', symbol: '⾍' },
+ 357→ { key: '0.48', symbol: '⾎' },
+ 358→ { key: '0.52', symbol: '⾏' },
+ 359→ { key: '0.58', symbol: '⾐' },
+ 360→ { key: '0.62', symbol: '⾑' },
+ 361→ { key: '0.72', symbol: '⾒' },
+ 362→ { key: '0.78', symbol: '⾓' },
+ 363→ { key: '0.82', symbol: '⾔' },
+ 364→ { key: '0.88', symbol: '⾕' },
+ 365→ { key: '1.1', symbol: '⾖' },
+ 366→ { key: '1.4', symbol: '⾗' },
+ 367→ { key: '1.6', symbol: '⾘' },
+ 368→ { key: '1.7', symbol: '⾙' },
+ 369→ { key: '1.8', symbol: '⾚' },
+ 370→ { key: '2.0', symbol: '⾛' },
+ 371→ { key: '2.2', symbol: '⾜' },
+ 372→ { key: '2.4', symbol: '⾝' },
+ 373→ { key: '2.6', symbol: '⾞' },
+ 374→ { key: '3.0', symbol: '⾟' },
+ 375→ { key: '3.5', symbol: '⾠' },
+ 376→ { key: '0.87', symbol: '⾡' },
+ 377→ { key: '0.24', symbol: '⾢' },
+ 378→ { key: '0.26', symbol: '⾣' },
+ 379→ { key: '0.27', symbol: '⾤' },
+ 380→ { key: '0.36', symbol: '⾥' },
+ 381→ // ── spaced operators (arrows) ──────────────────────────────────────────
+ 382→ { key: ' = ', symbol: '←' },
+ 383→ { key: ' + ', symbol: '→' },
+ 384→ { key: ' * ', symbol: '↑' },
+ 385→ { key: ' - ', symbol: '↓' },
+ 386→ { key: ' / ', symbol: '∙' },
+ 387→ { key: ', ', symbol: '⇄' },
+ 388→ // ── statement openers (double arrows) ──────────────────────────────────
+ 389→ { key: 'green = ', symbol: '⇩' },
+ 390→ { key: 'blue = ', symbol: '⇪' },
+ 391→ { key: 'x = ', symbol: '⇦' },
+ 392→ { key: 'y = ', symbol: '⇨' },
+ 393→ { key: 'red = ', symbol: '⇧' },
+ 394→ { key: 'hue = ', symbol: '⇱' },
+ 395→ { key: 't = ', symbol: '⇲' },
+ 396→ { key: 'n = ', symbol: '⇴' },
+ 397→]
+ 398→
+ 399→/** Noise chars platforms inject into copies (VS16, zero-widths). The
+ 400→ * encoder NEVER emits these raw — a script that legitimately contains one
+ 401→ * gets a dedicated symbol — so the decoder can strip every raw occurrence
+ 402→ * as injected mangling. */
+ 403→const NOISE_SYMBOLS = new Map([
+ 404→ ['\uFE0E', '⌸'],
+ 405→ ['\uFE0F', '⌹'],
+ 406→ ['\u200B', '⌺'],
+ 407→ ['\u200C', '⌻'],
+ 408→ ['\u200D', '⌼'],
+ 409→ ['\u2060', '⌽'],
+ 410→ ['\uFEFF', '⌾'],
+ 411→])
+ 412→const NOISE_CHAR_FROM_SYMBOL = new Map(
+ 413→ [...NOISE_SYMBOLS.entries()].map(([c, s]) => [s, c]),
+ 414→)
+ 415→
+ 416→// ── derived tables (built once, self-checked by construction) ─────────────
+ 417→
+ 418→/** Longest key first, so greedy matching always prefers the longest hit. */
+ 419→const DICT_SORTED = [...DICT].sort((a, b) => b.key.length - a.key.length)
+ 420→
+ 421→/** Entries grouped by first char, longest first — the encode lookup. */
+ 422→const BY_FIRST_CHAR = new Map()
+ 423→for (const entry of DICT_SORTED) {
+ 424→ const list = BY_FIRST_CHAR.get(entry.key[0])
+ 425→ if (list) list.push(entry)
+ 426→ else BY_FIRST_CHAR.set(entry.key[0], [entry])
+ 427→}
+ 428→
+ 429→/** Symbol → raw text (decode lookup). Throws on duplicate symbols — a
+ 430→ * broken table must fail loudly at import, never corrupt a share string. */
+ 431→const SYMBOL_TO_TEXT = new Map()
+ 432→for (const { key, symbol } of DICT) {
+ 433→ if (SYMBOL_TO_TEXT.has(symbol)) throw new Error(`share-codec: duplicate dictionary symbol ${symbol}`)
+ 434→ SYMBOL_TO_TEXT.set(symbol, key)
+ 435→}
+ 436→
+ 437→const ALL_SYMBOLS = new Set(DICT.map((e) => e.symbol))
+ 438→for (const s of NOISE_SYMBOLS.values()) ALL_SYMBOLS.add(s)
+ 439→
+ 440→/**
+ 441→ * The FROZEN bitmap list — every dictionary + noise symbol, sorted by code
+ 442→ * point. A ▼ header says "these of the list are in this string's alphabet"
+ 443→ * as a bitmap, 14 bits per digit character. This array is part of the ▼
+ 444→ * wire format: changing it breaks every ▼ string ever encoded (see the
+ 445→ * DICT compatibility contract above).
+ 446→ */
+ 447→const BITMAP_LIST: string[] = [...ALL_SYMBOLS].sort((a, b) => a.charCodeAt(0) - b.charCodeAt(0))
+ 448→const BITMAP_INDEX = new Map(BITMAP_LIST.map((s, i) => [s, i]))
+ 449→const DICT_BITMAP_DIGITS = Math.ceil(BITMAP_LIST.length / 14) // 14 bits per digit
+ 450→const ASCII_BITMAP_DIGITS = Math.ceil(128 / 14)
+ 451→
+ 452→/**
+ 453→ * Every character that can never appear raw in a body — emitted by the
+ 454→ * encoder only with a "\" escape in front (pair form) or folded into the
+ 455→ * bitmaps (base form). Covers the dictionary symbols, both scheme markers,
+ 456→ * the separator, the checksum alphabet (①–⑯), the control-picture range
+ 457→ * (␀–␡) and the invisible characters chat platforms inject.
+ 458→ */
+ 459→const RESERVED = new Set([ESCAPE, SCHEME_V1, SCHEME_V2, SEP])
+ 460→for (const s of ALL_SYMBOLS) RESERVED.add(s)
+ 461→for (let c = 0x2460; c <= 0x246f; c++) RESERVED.add(String.fromCharCode(c)) // ①–⑯
+ 462→for (let c = 0x2400; c <= 0x2421; c++) RESERVED.add(String.fromCharCode(c)) // ␀–␡
+ 463→for (const c of ['\uFE0E', '\uFE0F', '\u200B', '\u200C', '\u200D', '\u2060', '\uFEFF']) {
+ 464→ RESERVED.add(c)
+ 465→}
+ 466→
+ 467→const PLATFORM_NOISE = /[\uFE0E\uFE0F\u200B\u200C\u200D\u2060\uFEFF]/g
+ 468→
+ 469→/** Whitespace or platform noise — safe to shave off the string's edges. */
+ 470→function isEdgeTrimable(c: string): boolean {
+ 471→ return c === ' ' || c === '\n' || c === '\r' || c === '\t' || PLATFORM_NOISE.test(c)
+ 472→}
+ 473→
+ 474→// ── pair packing (shared by the ▼ fallback form and legacy ▲ strings) ──────
+ 475→
+ 476→/**
+ 477→ * Two adjacent characters from this 64-char alphabet fold into ONE code
+ 478→ * point at PAIR_BASE + 64·a + b (a common-CJK block — assigned since
+ 479→ * Unicode 1.1, text presentation, renders everywhere, copy/paste-safe).
+ 480→ * The block is reserved, so CJK that legitimately appears in a script is
+ 481→ * escaped and still round-trips.
+ 482→ */
+ 483→const PAIR_ALPHABET =
+ 484→ '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_.'
+ 485→const PAIR_BASE = 0x4e00
+ 486→const PAIR_END = PAIR_BASE + PAIR_ALPHABET.length * PAIR_ALPHABET.length - 1 // 0x5dff
+ 487→const PAIR_INDEX = new Map()
+ 488→for (let i = 0; i < PAIR_ALPHABET.length; i++) PAIR_INDEX.set(PAIR_ALPHABET[i], i)
+ 489→for (let c = PAIR_BASE; c <= PAIR_END; c++) RESERVED.add(String.fromCharCode(c))
+ 490→
+ 491→// ── big-base packing ───────────────────────────────────────────────────────
+ 492→
+ 493→/**
+ 494→ * The digit alphabet for the base form: CJK Unified Ideographs (20,992) +
+ 495→ * Hangul syllables (11,172) = 32,164 digits — every one assigned, text
+ 496→ * presentation, in every CJK-capable font, and copy/paste-safe. 14.98
+ 497→ * bits per character, vs 5.95 for classic base-62.
+ 498→ */
+ 499→const DIGIT_BLOCKS: Array<[number, number]> = [
+ 500→ [0x4e00, 0x9fff], // CJK Unified Ideographs
+ 501→ [0xac00, 0xd7a3], // Hangul syllables
+ 502→]
+ 503→const DIGIT_BLOCK0_SIZE = DIGIT_BLOCKS[0][1] - DIGIT_BLOCKS[0][0] + 1
+ 504→const BIG_BASE = DIGIT_BLOCK0_SIZE + (DIGIT_BLOCKS[1][1] - DIGIT_BLOCKS[1][0] + 1) // 32164
+ 505→const BIG_BASE_N = BigInt(BIG_BASE)
+ 506→const BITS_PER_DIGIT = 14 // floor(log2(32164)) -- bitmap digits carry 14 bits
+ 507→
+ 508→function digitChar(d: number): string {
+ 509→ return d < DIGIT_BLOCK0_SIZE
+ 510→ ? String.fromCharCode(DIGIT_BLOCKS[0][0] + d)
+ 511→ : String.fromCharCode(DIGIT_BLOCKS[1][0] + (d - DIGIT_BLOCK0_SIZE))
+ 512→}
+ 513→
+ 514→/** Digit char → value, or -1 when the char isn't a digit. */
+ 515→function digitValue(c: string): number {
+ 516→ const cc = c.charCodeAt(0)
+ 517→ if (cc >= DIGIT_BLOCKS[0][0] && cc <= DIGIT_BLOCKS[0][1]) return cc - DIGIT_BLOCKS[0][0]
+ 518→ if (cc >= DIGIT_BLOCKS[1][0] && cc <= DIGIT_BLOCKS[1][1]) return cc - DIGIT_BLOCKS[1][0] + DIGIT_BLOCK0_SIZE
+ 519→ return -1
+ 520→}
+ 521→
+ 522→/** Bits → exactly `digits` digit chars (left-padded with digit 0). */
+ 523→function bitsToDigits(bits: boolean[], digits: number): string {
+ 524→ let value = 0n
+ 525→ for (const b of bits) value = value * 2n + (b ? 1n : 0n)
+ 526→ const ds: number[] = []
+ 527→ let v = value
+ 528→ while (v > 0n) {
+ 529→ ds.push(Number(v % BIG_BASE_N))
+ 530→ v /= BIG_BASE_N
+ 531→ }
+ 532→ while (ds.length < digits) ds.push(0)
+ 533→ if (ds.length > digits) throw new Error('share-codec: bitmap overflow')
+ 534→ let out = ''
+ 535→ for (let k = ds.length - 1; k >= 0; k--) out += digitChar(ds[k])
+ 536→ return out
+ 537→}
+ 538→
+ 539→/** The first `count` digit chars of `s` → bits (big-endian, `bits` long). */
+ 540→function digitsToBits(s: string, count: number, bits: number): boolean[] {
+ 541→ let value = 0n
+ 542→ for (let i = 0; i < count; i++) {
+ 543→ const d = digitValue(s[i])
+ 544→ if (d < 0) throw new Error('share-codec: bad bitmap digit')
+ 545→ value = value * BIG_BASE_N + BigInt(d)
+ 546→ }
+ 547→ const out: boolean[] = []
+ 548→ for (let i = bits - 1; i >= 0; i--) out.push((value >> BigInt(i)) & 1n ? true : false)
+ 549→ return out
+ 550→}
+ 551→
+ 552→/**
+ 553→ * Largest body (in symbols) the big-base packer will accept — packing is
+ 554→ * O(n²) in BigInt arithmetic, and 16,000 symbols packs in ~150 ms while a
+ 555→ * 50,000-symbol body would take seconds. Longer scripts fall back to the
+ 556→ * linear pair form (still ~55% shorter).
+ 557→ */
+ 558→const PACK_SYMBOL_LIMIT = 16_000
+ 559→
+ 560→/**
+ 561→ * Pack a dictionary body into the base form:
+ 562→ *
+ 563→ * 〠
+ 564→ *
+ 565→ * The payload is the whole body as one big number (a leading sentinel
+ 566→ * symbol — the alphabet's last — guarantees leading-alphabet-symbol zeros
+ 567→ * survive). The header rebuilds the body's alphabet for the decoder: which
+ 568→ * frozen dictionary symbols are used (bitmap), which ASCII chars are used
+ 569→ * (bitmap), and any remaining non-ASCII chars verbatim.
+ 570→ */
+ 571→function packBase(body: string): string | null {
+ 572→ const n = body.length
+ 573→ if (n < 2 || n > PACK_SYMBOL_LIMIT) return null
+ 574→
+ 575→ const used = new Set()
+ 576→ for (let i = 0; i < n; i++) used.add(body[i])
+ 577→ const alphabet = [...used].sort((a, b) => a.charCodeAt(0) - b.charCodeAt(0))
+ 578→ const m = alphabet.length
+ 579→ if (m < 2) return null
+ 580→
+ 581→ const indexOf = new Map()
+ 582→ alphabet.forEach((s, idx) => indexOf.set(s, idx))
+ 583→
+ 584→ // value = sentinel·m^n + body as a base-m number
+ 585→ let value = BigInt(m - 1)
+ 586→ for (let i = 0; i < n; i++) value = value * BigInt(m) + BigInt(indexOf.get(body[i])!)
+ 587→
+ 588→ let payload = ''
+ 589→ const ds: number[] = []
+ 590→ let v = value
+ 591→ while (v > 0n) {
+ 592→ ds.push(Number(v % BIG_BASE_N))
+ 593→ v /= BIG_BASE_N
+ 594→ }
+ 595→ for (let k = ds.length - 1; k >= 0; k--) payload += digitChar(ds[k])
+ 596→
+ 597→ // header bitmaps
+ 598→ const dictBits: boolean[] = []
+ 599→ for (const s of BITMAP_LIST) dictBits.push(used.has(s))
+ 600→ const asciiBits: boolean[] = []
+ 601→ for (let c = 0; c < 128; c++) asciiBits.push(used.has(String.fromCharCode(c)))
+ 602→ let tail = ''
+ 603→ for (const s of alphabet) {
+ 604→ const cc = s.charCodeAt(0)
+ 605→ if (cc >= 128 && !BITMAP_INDEX.has(s)) tail += s
+ 606→ }
+ 607→
+ 608→ return (
+ 609→ payload + SEP + bitsToDigits(dictBits, DICT_BITMAP_DIGITS) + bitsToDigits(asciiBits, ASCII_BITMAP_DIGITS) + tail
+ 610→ )
+ 611→}
+ 612→
+ 613→/** Inverse of packBase — returns null on any structural violation. */
+ 614→function unpackBase(packed: string): string | null {
+ 615→ // first bare SEP splits payload | header (payload digits are CJK/Hangul,
+ 616→ // so no digit can ever be mistaken for the separator)
+ 617→ let sepIdx = -1
+ 618→ for (let i = 0; i < packed.length; i++) {
+ 619→ if (packed[i] === SEP) {
+ 620→ sepIdx = i
+ 621→ break
+ 622→ }
+ 623→ }
+ 624→ if (sepIdx < 1 || sepIdx === packed.length - 1) return null
+ 625→
+ 626→ let value = 0n
+ 627→ for (let i = 0; i < sepIdx; i++) {
+ 628→ const d = digitValue(packed[i])
+ 629→ if (d < 0) return null
+ 630→ value = value * BIG_BASE_N + BigInt(d)
+ 631→ }
+ 632→ if (value <= 0n) return null
+ 633→
+ 634→ const header = packed.slice(sepIdx + 1)
+ 635→ if (header.length < DICT_BITMAP_DIGITS + ASCII_BITMAP_DIGITS) return null
+ 636→ let dictBits: boolean[]
+ 637→ let asciiBits: boolean[]
+ 638→ try {
+ 639→ dictBits = digitsToBits(header, DICT_BITMAP_DIGITS, BITMAP_LIST.length)
+ 640→ asciiBits = digitsToBits(header.slice(DICT_BITMAP_DIGITS), ASCII_BITMAP_DIGITS, 128)
+ 641→ } catch {
+ 642→ return null
+ 643→ }
+ 644→ const tail = header.slice(DICT_BITMAP_DIGITS + ASCII_BITMAP_DIGITS)
+ 645→
+ 646→ // rebuild the alphabet: frozen symbols + ASCII + verbatim tail, sorted
+ 647→ const alphabet: string[] = []
+ 648→ dictBits.forEach((on, i) => {
+ 649→ if (on) alphabet.push(BITMAP_LIST[i])
+ 650→ })
+ 651→ asciiBits.forEach((on, c) => {
+ 652→ if (on) alphabet.push(String.fromCharCode(c))
+ 653→ })
+ 654→ const seen = new Set(alphabet)
+ 655→ for (let i = 0; i < tail.length; i++) {
+ 656→ const c = tail[i]
+ 657→ if (seen.has(c)) return null // tail must be deduped
+ 658→ seen.add(c)
+ 659→ alphabet.push(c)
+ 660→ }
+ 661→ alphabet.sort((a, b) => a.charCodeAt(0) - b.charCodeAt(0))
+ 662→
+ 663→ const m = alphabet.length
+ 664→ if (m < 2) return null
+ 665→ const M = BigInt(m)
+ 666→ const idxs: number[] = []
+ 667→ while (value > 0n) {
+ 668→ idxs.push(Number(value % M))
+ 669→ value /= M
+ 670→ }
+ 671→ let s = ''
+ 672→ for (let k = idxs.length - 1; k >= 0; k--) {
+ 673→ const idx = idxs[k]
+ 674→ if (idx >= m) return null
+ 675→ s += alphabet[idx]
+ 676→ }
+ 677→ if (s.length < 1) return null
+ 678→ return s.slice(1) // strip the sentinel
+ 679→}
+ 680→
+ 681→// ── checksum ──────────────────────────────────────────────────────────────
+ 682→
+ 683→/** FNV-1a 32-bit over the UTF-16 code units — fast, tiny, plenty here. */
+ 684→function fnv1a(s: string): number {
+ 685→ let h = 0x811c9dc5
+ 686→ for (let i = 0; i < s.length; i++) {
+ 687→ h ^= s.charCodeAt(i)
+ 688→ h = Math.imul(h, 0x01000193) >>> 0
+ 689→ }
+ 690→ return h >>> 0
+ 691→}
+ 692→
+ 693→/** Encode a 32-bit digest as 8 "circled digit" hex chars (① = 0 … ⑯ = f). */
+ 694→function checkDigits(value: number): string {
+ 695→ let out = ''
+ 696→ for (let shift = 28; shift >= 0; shift -= 4) {
+ 697→ out += String.fromCharCode(0x2460 + ((value >>> shift) & 0xf))
+ 698→ }
+ 699→ return out
+ 700→}
+ 701→
+ 702→function isCheckDigit(c: string): boolean {
+ 703→ const cc = c.charCodeAt(0)
+ 704→ return cc >= 0x2460 && cc <= 0x246f
+ 705→}
+ 706→
+ 707→// ── name label ────────────────────────────────────────────────────────────
+ 708→
+ 709→/** Pull the `name:` header out of a script (before the first [section]). */
+ 710→function extractScriptName(script: string): string {
+ 711→ for (const line of script.split('\n')) {
+ 712→ const t = line.trim()
+ 713→ if (t.startsWith('[')) break
+ 714→ const m = /^name\s*:\s*(.*)$/i.exec(t)
+ 715→ if (m) {
+ 716→ let v = m[1].trim()
+ 717→ if (
+ 718→ v.length >= 2 &&
+ 719→ ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'")))
+ 720→ ) {
+ 721→ v = v.slice(1, -1).trim()
+ 722→ }
+ 723→ if (v) return v
+ 724→ }
+ 725→ }
+ 726→ return ''
+ 727→}
+ 728→
+ 729→/** Make the label safe for the `Name:[…]` wrapper (no : [ ] or newlines). */
+ 730→function sanitizeShareName(raw: string): string {
+ 731→ const cleaned = raw
+ 732→ .replace(/[\[\]:\r\n\t]+/g, ' ')
+ 733→ .replace(/\s+/g, ' ')
+ 734→ .trim()
+ 735→ .slice(0, 48)
+ 736→ .trim()
+ 737→ return cleaned || 'OVE'
+ 738→}
+ 739→
+ 740→// ── encode ────────────────────────────────────────────────────────────────
+ 741→
+ 742→/** Substitute the dictionary into the script text (escaping as needed). */
+ 743→function encodeDictBody(script: string): string {
+ 744→ let out = ''
+ 745→ let i = 0
+ 746→ const n = script.length
+ 747→ while (i < n) {
+ 748→ const c = script[i]
+ 749→ // legitimate noise chars → their dedicated symbols (never raw)
+ 750→ const noiseSym = NOISE_SYMBOLS.get(c)
+ 751→ if (noiseSym !== undefined) {
+ 752→ out += noiseSym
+ 753→ i++
+ 754→ continue
+ 755→ }
+ 756→ // reserved / escape char → escaped literal
+ 757→ if (c === ESCAPE || RESERVED.has(c)) {
+ 758→ out += ESCAPE + c
+ 759→ i++
+ 760→ continue
+ 761→ }
+ 762→ // dictionary match (longest first) — also folds \n \r \t into symbols
+ 763→ const entries = BY_FIRST_CHAR.get(c)
+ 764→ if (entries) {
+ 765→ let matched = false
+ 766→ for (const e of entries) {
+ 767→ if (script.startsWith(e.key, i)) {
+ 768→ out += e.symbol
+ 769→ i += e.key.length
+ 770→ matched = true
+ 771→ break
+ 772→ }
+ 773→ }
+ 774→ if (matched) continue
+ 775→ }
+ 776→ // other control characters → Unicode control pictures (copy-safe)
+ 777→ const cc = c.charCodeAt(0)
+ 778→ if (cc < 0x20 || cc === 0x7f) {
+ 779→ out += cc === 0x7f ? '\u2421' : String.fromCharCode(0x2400 + cc)
+ 780→ i++
+ 781→ continue
+ 782→ }
+ 783→ out += c
+ 784→ i++
+ 785→ }
+ 786→ return out
+ 787→}
+ 788→
+ 789→/** Dictionary + pair packing interleaved (the fallback form, v7.7-style). */
+ 790→function encodePairBody(script: string): string {
+ 791→ let out = ''
+ 792→ let i = 0
+ 793→ const n = script.length
+ 794→ while (i < n) {
+ 795→ const c = script[i]
+ 796→ const noiseSym = NOISE_SYMBOLS.get(c)
+ 797→ if (noiseSym !== undefined) {
+ 798→ out += noiseSym
+ 799→ i++
+ 800→ continue
+ 801→ }
+ 802→ if (c === ESCAPE || RESERVED.has(c)) {
+ 803→ out += ESCAPE + c
+ 804→ i++
+ 805→ continue
+ 806→ }
+ 807→ const entries = BY_FIRST_CHAR.get(c)
+ 808→ if (entries) {
+ 809→ let matched = false
+ 810→ for (const e of entries) {
+ 811→ if (script.startsWith(e.key, i)) {
+ 812→ out += e.symbol
+ 813→ i += e.key.length
+ 814→ matched = true
+ 815→ break
+ 816→ }
+ 817→ }
+ 818→ if (matched) continue
+ 819→ }
+ 820→ const cc = c.charCodeAt(0)
+ 821→ if (cc < 0x20 || cc === 0x7f) {
+ 822→ out += cc === 0x7f ? '\u2421' : String.fromCharCode(0x2400 + cc)
+ 823→ i++
+ 824→ continue
+ 825→ }
+ 826→ // pair packing — two adjacent [0-9a-zA-Z_.] chars → one CJK code point
+ 827→ const ai = PAIR_INDEX.get(c)
+ 828→ if (ai !== undefined) {
+ 829→ const bi = PAIR_INDEX.get(script[i + 1])
+ 830→ if (bi !== undefined) {
+ 831→ out += String.fromCharCode(PAIR_BASE + ai * PAIR_ALPHABET.length + bi)
+ 832→ i += 2
+ 833→ continue
+ 834→ }
+ 835→ }
+ 836→ out += c
+ 837→ i++
+ 838→ }
+ 839→ return out
+ 840→}
+ 841→
+ 842→export interface ScriptHash {
+ 843→ /** The full share string: `Name:[▼…①①①…]`. */
+ 844→ hash: string
+ 845→ /** The label used in the prefix (sanitized). */
+ 846→ name: string
+ 847→ /** script.length (UTF-16 units) — for the stats line. */
+ 848→ scriptLength: number
+ 849→ /** hash.length — for the stats line. */
+ 850→ hashLength: number
+ 851→}
+ 852→
+ 853→/**
+ 854→ * Encode a script into its share string. Never throws, never validates the
+ 855→ * script — sharing a script that doesn't compile is a feature (ask for help,
+ 856→ * get it debugged). `name` overrides the label; by default the `name:`
+ 857→ * header is used, falling back to "OVE".
+ 858→ */
+ 859→export function encodeScriptHash(script: string, name?: string): ScriptHash {
+ 860→ const label = sanitizeShareName(name ?? extractScriptName(script))
+ 861→ const dictBody = encodeDictBody(script)
+ 862→ const packed = packBase(dictBody)
+ 863→ let body: string
+ 864→ if (packed !== null) {
+ 865→ const pairBody = encodePairBody(script)
+ 866→ body = packed.length < pairBody.length ? packed : pairBody
+ 867→ } else {
+ 868→ body = encodePairBody(script)
+ 869→ }
+ 870→ const hash = `${label}:[${SCHEME_V2}${body}${checkDigits(fnv1a(script))}]`
+ 871→ return { hash, name: label, scriptLength: script.length, hashLength: hash.length }
+ 872→}
+ 873→
+ 874→// ── decode ────────────────────────────────────────────────────────────────
+ 875→
+ 876→/** Expand a dictionary body back into the original text. */
+ 877→function decodeDictBody(body: string): string {
+ 878→ let out = ''
+ 879→ for (let i = 0; i < body.length; i++) {
+ 880→ const c = body[i]
+ 881→ if (c === ESCAPE) {
+ 882→ const next = body[i + 1]
+ 883→ if (next !== undefined) {
+ 884→ out += next
+ 885→ i++
+ 886→ } else {
+ 887→ out += c // trailing lone escape -- be lenient
+ 888→ }
+ 889→ continue
+ 890→ }
+ 891→ const cc = c.charCodeAt(0)
+ 892→ // control pictures → the control chars they stand for
+ 893→ if (cc >= 0x2400 && cc <= 0x2420) {
+ 894→ out += String.fromCharCode(cc - 0x2400)
+ 895→ continue
+ 896→ }
+ 897→ if (cc === 0x2421) {
+ 898→ out += '\u007f'
+ 899→ continue
+ 900→ }
+ 901→ // dictionary symbols → raw text
+ 902→ const expansion = SYMBOL_TO_TEXT.get(c)
+ 903→ if (expansion !== undefined) {
+ 904→ out += expansion
+ 905→ continue
+ 906→ }
+ 907→ // legitimate noise symbols → their original chars
+ 908→ const noiseChar = NOISE_CHAR_FROM_SYMBOL.get(c)
+ 909→ if (noiseChar !== undefined) {
+ 910→ out += noiseChar
+ 911→ continue
+ 912→ }
+ 913→ out += c
+ 914→ }
+ 915→ return out
+ 916→}
+ 917→
+ 918→/** Expand a pair-form body (dictionary + pair slots) into the script. */
+ 919→function decodePairBody(body: string): string {
+ 920→ let out = ''
+ 921→ for (let i = 0; i < body.length; i++) {
+ 922→ const c = body[i]
+ 923→ if (c === ESCAPE) {
+ 924→ const next = body[i + 1]
+ 925→ if (next !== undefined) {
+ 926→ out += next
+ 927→ i++
+ 928→ } else {
+ 929→ out += c
+ 930→ }
+ 931→ continue
+ 932→ }
+ 933→ const cc = c.charCodeAt(0)
+ 934→ if (cc >= 0x2400 && cc <= 0x2420) {
+ 935→ out += String.fromCharCode(cc - 0x2400)
+ 936→ continue
+ 937→ }
+ 938→ if (cc === 0x2421) {
+ 939→ out += '\u007f'
+ 940→ continue
+ 941→ }
+ 942→ const expansion = SYMBOL_TO_TEXT.get(c)
+ 943→ if (expansion !== undefined) {
+ 944→ out += expansion
+ 945→ continue
+ 946→ }
+ 947→ const noiseChar = NOISE_CHAR_FROM_SYMBOL.get(c)
+ 948→ if (noiseChar !== undefined) {
+ 949→ out += noiseChar
+ 950→ continue
+ 951→ }
+ 952→ if (cc >= PAIR_BASE && cc <= PAIR_END) {
+ 953→ const k = cc - PAIR_BASE
+ 954→ out += PAIR_ALPHABET[Math.floor(k / PAIR_ALPHABET.length)] + PAIR_ALPHABET[k % PAIR_ALPHABET.length]
+ 955→ continue
+ 956→ }
+ 957→ out += c
+ 958→ }
+ 959→ return out
+ 960→}
+ 961→
+ 962→// ── legacy ▲ support (v7.7 strings — decode only) ──────────────────────────
+ 963→
+ 964→const LEGACY_DICT: DictEntry[] = [
+ 965→ { key: '\n', symbol: '♤' },
+ 966→ { key: ' ', symbol: '♦' },
+ 967→ { key: 'beats', symbol: '♡' },
+ 968→ { key: 'beatage', symbol: '♠' },
+ 969→ { key: '\t', symbol: '♣' },
+ 970→ { key: ' ', symbol: '▣' },
+ 971→ { key: ' ', symbol: '·' },
+ 972→ { key: '\r', symbol: '⏎' },
+ 973→ { key: '[init]', symbol: '♩' },
+ 974→ { key: '[frame]', symbol: '♪' },
+ 975→ { key: '[beat]', symbol: '♫' },
+ 976→ { key: '[point]', symbol: '♬' },
+ 977→ { key: '# ', symbol: '♯' },
+ 978→ { key: '#', symbol: '♭' },
+ 979→ { key: '//', symbol: '♮' },
+ 980→ { key: 'name', symbol: '♔' },
+ 981→ { key: 'author', symbol: '♕' },
+ 982→ { key: 'description', symbol: '♖' },
+ 983→ { key: 'mode', symbol: '♗' },
+ 984→ { key: 'blend', symbol: '♘' },
+ 985→ { key: 'persist', symbol: '♙' },
+ 986→ { key: 'linewidth', symbol: '♚' },
+ 987→ { key: 'ysign', symbol: '♛' },
+ 988→ { key: 'lines', symbol: '♈' },
+ 989→ { key: 'dots', symbol: '♉' },
+ 990→ { key: 'solid', symbol: '♊' },
+ 991→ { key: 'additive', symbol: '♋' },
+ 992→ { key: 'alpha', symbol: '♌' },
+ 993→ { key: ' = ', symbol: '←' },
+ 994→ { key: ' + ', symbol: '→' },
+ 995→ { key: ' * ', symbol: '↑' },
+ 996→ { key: ' - ', symbol: '↓' },
+ 997→ { key: ', ', symbol: '⇄' },
+ 998→ { key: 'x = ', symbol: '⇦' },
+ 999→ { key: 'y = ', symbol: '⇨' },
+ 1000→ { key: 'red = ', symbol: '⇧' },
+ 1001→ { key: 'green = ', symbol: '⇩' },
+ 1002→ { key: 'blue = ', symbol: '⇪' },
+ 1003→ { key: 'hue = ', symbol: '⇱' },
+ 1004→ { key: 't = ', symbol: '⇲' },
+ 1005→ { key: 'n = ', symbol: '⇴' },
+ 1006→ { key: 'bass_att', symbol: '♎' },
+ 1007→ { key: 'mid_att', symbol: '♏' },
+ 1008→ { key: 'treb_att', symbol: '♐' },
+ 1009→ { key: 'bass', symbol: '♑' },
+ 1010→ { key: 'mid', symbol: '♒' },
+ 1011→ { key: 'treb', symbol: '♓' },
+ 1012→ { key: 'vol', symbol: '☽' },
+ 1013→ { key: 'beat', symbol: '◉' },
+ 1014→ { key: 'red', symbol: '☾' },
+ 1015→ { key: 'green', symbol: '☿' },
+ 1016→ { key: 'blue', symbol: '⚑' },
+ 1017→ { key: 'skip', symbol: '⚐' },
+ 1018→ { key: 'aspect', symbol: '⚶' },
+ 1019→ { key: 'dt', symbol: '◐' },
+ 1020→ { key: 'pi', symbol: '◑' },
+ 1021→ { key: 'tau', symbol: '◒' },
+ 1022→ { key: 'smoothstep', symbol: '⌒' },
+ 1023→ { key: 'gmegabuf', symbol: '✥' },
+ 1024→ { key: 'getkbmouse', symbol: '◶' },
+ 1025→ { key: 'megabuf', symbol: '✤' },
+ 1026→ { key: 'getspec', symbol: '▤' },
+ 1027→ { key: 'getosc', symbol: '▥' },
+ 1028→ { key: 'gettime', symbol: '◷' },
+ 1029→ { key: 'atan2', symbol: '⌗' },
+ 1030→ { key: 'log10', symbol: '㏑' },
+ 1031→ { key: 'invsqrt', symbol: '√' },
+ 1032→ { key: 'sigmoid', symbol: '∿' },
+ 1033→ { key: 'inrange', symbol: '⋔' },
+ 1034→ { key: 'hsvr', symbol: '⬥' },
+ 1035→ { key: 'hsvg', symbol: '⬦' },
+ 1036→ { key: 'hsvb', symbol: '⬧' },
+ 1037→ { key: 'above', symbol: '≻' },
+ 1038→ { key: 'below', symbol: '≺' },
+ 1039→ { key: 'asin', symbol: '⌠' },
+ 1040→ { key: 'acos', symbol: '⌡' },
+ 1041→ { key: 'atan', symbol: '⌐' },
+ 1042→ { key: 'ceil', symbol: '⌈' },
+ 1043→ { key: 'floor', symbol: '⌊' },
+ 1044→ { key: 'sqr', symbol: '□' },
+ 1045→ { key: 'sqrt', symbol: '∛' },
+ 1046→ { key: 'pow', symbol: '⌃' },
+ 1047→ { key: 'exp', symbol: 'ℯ' },
+ 1048→ { key: 'log', symbol: '㏒' },
+ 1049→ { key: 'round', symbol: '◎' },
+ 1050→ { key: 'int', symbol: '⊺' },
+ 1051→ { key: 'frac', symbol: '∵' },
+ 1052→ { key: 'sign', symbol: '±' },
+ 1053→ { key: 'bnot', symbol: '¬' },
+ 1054→ { key: 'band', symbol: '∧' },
+ 1055→ { key: 'bor', symbol: '∨' },
+ 1056→ { key: 'equal', symbol: '≟' },
+ 1057→ { key: 'lerp', symbol: '↝' },
+ 1058→ { key: 'clamp', symbol: '⊜' },
+ 1059→ { key: 'rand', symbol: '⚄' },
+ 1060→ { key: 'rnd', symbol: '⚅' },
+ 1061→ { key: 'sin', symbol: '◠' },
+ 1062→ { key: 'cos', symbol: '◡' },
+ 1063→ { key: 'tan', symbol: '◮' },
+ 1064→ { key: 'abs', symbol: '¦' },
+ 1065→ { key: 'min', symbol: '≪' },
+ 1066→ { key: 'max', symbol: '≫' },
+ 1067→ { key: 'if', symbol: '∴' },
+ 1068→ { key: 'loop', symbol: '↻' },
+ 1069→ { key: '6.28318', symbol: '⌇' },
+ 1070→ { key: '0.5', symbol: '†' },
+ 1071→ { key: '0.3', symbol: '‡' },
+ 1072→ { key: '0.25', symbol: '•' },
+ 1073→ { key: '0.55', symbol: '§' },
+ 1074→ { key: '0.45', symbol: '¶' },
+ 1075→ { key: '0.35', symbol: '※' },
+ 1076→ { key: '0.4', symbol: '⊤' },
+ 1077→ { key: '0.7', symbol: '⊥' },
+ 1078→ { key: '0.85', symbol: '⊢' },
+ 1079→ { key: '0.6', symbol: '⊣' },
+ 1080→ { key: '1.3', symbol: '⋈' },
+ 1081→ { key: '1.2', symbol: '⋉' },
+ 1082→ { key: ' / ', symbol: '∙' },
+ 1083→ { key: 'hue', symbol: '⋊' },
+ 1084→ { key: 'rad', symbol: '⋅' },
+ 1085→ { key: 'flash', symbol: '∘' },
+ 1086→]
+ 1087→
+ 1088→const LEGACY_SYMBOL_TO_TEXT = new Map()
+ 1089→for (const { key, symbol } of LEGACY_DICT) LEGACY_SYMBOL_TO_TEXT.set(symbol, key)
+ 1090→for (const [c, s] of NOISE_SYMBOLS) LEGACY_SYMBOL_TO_TEXT.set(s, c)
+ 1091→
+ 1092→/** Expand a v7.7 ▲ body. */
+ 1093→function decodeLegacyBody(body: string): string {
+ 1094→ let out = ''
+ 1095→ for (let i = 0; i < body.length; i++) {
+ 1096→ const c = body[i]
+ 1097→ if (c === ESCAPE) {
+ 1098→ const next = body[i + 1]
+ 1099→ if (next !== undefined) {
+ 1100→ out += next
+ 1101→ i++
+ 1102→ } else {
+ 1103→ out += c
+ 1104→ }
+ 1105→ continue
+ 1106→ }
+ 1107→ const cc = c.charCodeAt(0)
+ 1108→ if (cc >= 0x2400 && cc <= 0x2420) {
+ 1109→ out += String.fromCharCode(cc - 0x2400)
+ 1110→ continue
+ 1111→ }
+ 1112→ if (cc === 0x2421) {
+ 1113→ out += '\u007f'
+ 1114→ continue
+ 1115→ }
+ 1116→ const expansion = LEGACY_SYMBOL_TO_TEXT.get(c)
+ 1117→ if (expansion !== undefined) {
+ 1118→ out += expansion
+ 1119→ continue
+ 1120→ }
+ 1121→ if (cc >= PAIR_BASE && cc <= PAIR_END) {
+ 1122→ const k = cc - PAIR_BASE
+ 1123→ out += PAIR_ALPHABET[Math.floor(k / PAIR_ALPHABET.length)] + PAIR_ALPHABET[k % PAIR_ALPHABET.length]
+ 1124→ continue
+ 1125→ }
+ 1126→ out += c
+ 1127→ }
+ 1128→ return out
+ 1129→}
+ 1130→
+ 1131→// ── decode entry point ────────────────────────────────────────────────────
+ 1132→
+ 1133→export type HashDecodeResult =
+ 1134→ | { ok: true; script: string; name: string | null }
+ 1135→ | { ok: false; error: string }
+ 1136→
+ 1137→/**
+ 1138→ * Decode a pasted share string back into the original script. Accepts the
+ 1139→ * full `Name:[…]` form or a bare `[…]` body, ▼ (current) and ▲ (v7.7)
+ 1140→ * schemes; strips the invisible characters platforms sprinkle into copies;
+ 1141→ * and verifies the check digits so a truncated or altered string is
+ 1142→ * rejected instead of decoding into a corrupted script.
+ 1143→ */
+ 1144→export function decodeScriptHash(input: string): HashDecodeResult {
+ 1145→ if (input.length > 40_000) {
+ 1146→ return { ok: false, error: 'That text is far too long to be a share string.' }
+ 1147→ }
+ 1148→
+ 1149→ // Trim whitespace AND stray noise from the edges (the encoder never emits
+ 1150→ // either raw, so edge removal is always safe).
+ 1151→ let text = input
+ 1152→ while (text.length > 0 && isEdgeTrimable(text[0])) text = text.slice(1)
+ 1153→ while (text.length > 0 && isEdgeTrimable(text[text.length - 1])) text = text.slice(0, -1)
+ 1154→ if (
+ 1155→ text.length >= 2 &&
+ 1156→ ((text.startsWith('"') && text.endsWith('"')) || (text.startsWith("'") && text.endsWith("'")))
+ 1157→ ) {
+ 1158→ text = text.slice(1, -1)
+ 1159→ while (text.length > 0 && isEdgeTrimable(text[text.length - 1])) text = text.slice(0, -1)
+ 1160→ }
+ 1161→ // Any raw noise left is injected mangling (legit noise is symbol-coded),
+ 1162→ // so it can be stripped everywhere without ever eating original content.
+ 1163→ text = text.replace(PLATFORM_NOISE, '')
+ 1164→
+ 1165→ // `Name:[▼…]`, `[▼…]`, `Name:[▲…]` or `[▲…]`
+ 1166→ let payload: string | null = null
+ 1167→ let name: string | null = null
+ 1168→ const full = /^([\s\S]*?):\[([▼▲][\s\S]*)\]$/.exec(text)
+ 1169→ if (full) {
+ 1170→ name = full[1].trim() || null
+ 1171→ payload = full[2]
+ 1172→ } else if (/^\[([▼▲][\s\S]*)\]$/.test(text)) {
+ 1173→ payload = text.slice(1, -1)
+ 1174→ }
+ 1175→ if (payload === null) {
+ 1176→ // a string that still shows the "…:[▼/▲" signature was cut off
+ 1177→ // somewhere after the opening — a character limit ate the end
+ 1178→ const looksCutOff = /:\[[▼▲]/.test(text) || /^\[[▼▲]/.test(text)
+ 1179→ return {
+ 1180→ ok: false,
+ 1181→ error: looksCutOff
+ 1182→ ? 'This share string looks cut off -- it should end with "]". Copy the whole string and try again.'
+ 1183→ : 'This does not look like an OVE share string -- it should read like "My Script:[▼…]".',
+ 1184→ }
+ 1185→ }
+ 1186→
+ 1187→ // trailing 8 check digits
+ 1188→ if (payload.length < 9 || !isCheckDigit(payload[payload.length - 8])) {
+ 1189→ return {
+ 1190→ ok: false,
+ 1191→ error: 'This share string looks cut off -- the 8 check digits at its end are missing. Copy the whole string and try again.',
+ 1192→ }
+ 1193→ }
+ 1194→ const digits = payload.slice(-8)
+ 1195→ for (const d of digits) {
+ 1196→ if (!isCheckDigit(d)) {
+ 1197→ return {
+ 1198→ ok: false,
+ 1199→ error: 'This share string looks cut off -- the 8 check digits at its end are missing. Copy the whole string and try again.',
+ 1200→ }
+ 1201→ }
+ 1202→ }
+ 1203→ const marker = payload[0]
+ 1204→ const body = payload.slice(1, -8)
+ 1205→
+ 1206→ let script: string
+ 1207→ if (marker === SCHEME_V2) {
+ 1208→ // base form ⇔ the body contains a bare (unescaped) 〠 — the separator
+ 1209→ // between payload digits and the bitmap header. The pair form escapes
+ 1210→ // every 〠, and base-form payload digits can never be one.
+ 1211→ let hasBareSep = false
+ 1212→ for (let i = 0; i < body.length; i++) {
+ 1213→ if (body[i] === ESCAPE) i++
+ 1214→ else if (body[i] === SEP) {
+ 1215→ hasBareSep = true
+ 1216→ break
+ 1217→ }
+ 1218→ }
+ 1219→ if (hasBareSep) {
+ 1220→ const dictBody = unpackBase(body)
+ 1221→ if (dictBody === null) {
+ 1222→ return {
+ 1223→ ok: false,
+ 1224→ error: 'This share string is damaged -- its packing header does not parse. Copy the whole string and try again.',
+ 1225→ }
+ 1226→ }
+ 1227→ script = decodeDictBody(dictBody)
+ 1228→ } else {
+ 1229→ script = decodePairBody(body)
+ 1230→ }
+ 1231→ } else {
+ 1232→ script = decodeLegacyBody(body)
+ 1233→ }
+ 1234→
+ 1235→ if (checkDigits(fnv1a(script)) !== digits) {
+ 1236→ return {
+ 1237→ ok: false,
+ 1238→ error: 'The check digits do not match -- the string was altered or cut off while copying. Copy the whole string and try again.',
+ 1239→ }
+ 1240→ }
+ 1241→ return { ok: true, script, name }
+ 1242→}
+ 1243→
+ 1244→// ── legend for the UI ─────────────────────────────────────────────────────
+ 1245→
+ 1246→/** A few representative symbol meanings, shown in the share dialogs. */
+ 1247→export const SHARE_LEGEND: ReadonlyArray<{ symbol: string; meaning: string }> = [
+ 1248→ { symbol: '♤', meaning: 'line return' },
+ 1249→ { symbol: '♦', meaning: 'space' },
+ 1250→ { symbol: '♡', meaning: 'beats' },
+ 1251→ { symbol: '♠', meaning: 'beatage' },
+ 1252→ { symbol: '一', meaning: 'one packed digit -- 15 bits of script' },
+ 1253→]
+ 1254→
\ No newline at end of file
diff --git a/tool-results/read_1793817160288_8d6de58d6d8d.txt b/tool-results/read_1793817160288_8d6de58d6d8d.txt
new file mode 100644
index 0000000..6cf0160
--- /dev/null
+++ b/tool-results/read_1793817160288_8d6de58d6d8d.txt
@@ -0,0 +1,1106 @@
+ 1→// OVE — Presets tab: folder/file tree manager (v6.2 security model).
+ 2→//
+ 3→// The tree merges two worlds:
+ 4→// • root/embedded — the shipped library, READ-ONLY for every visitor (lock
+ 5→// badge in the tree; the server refuses all writes). Selecting an embedded
+ 6→// preset offers "Copy to my presets" instead of rename/delete.
+ 7→// • root/user created — this visitor's OWN presets, stored in their browser
+ 8→// (localStorage). Create / rename / delete / import all happen locally —
+ 9→// nothing is sent to the server, so nobody can spam or vandalize what
+ 10→// other visitors see.
+ 11→//
+ 12→// Left column: the tree (chevron expands, row selects, double-click runs,
+ 13→// running preset flagged emerald, originals download from the row).
+ 14→// Right column: context-sensitive management + the club-mode randomizer.
+ 15→
+ 16→'use client'
+ 17→
+ 18→import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+ 19→import {
+ 20→ ChevronRight,
+ 21→ CopyPlus,
+ 22→ Download,
+ 23→ FilePlus2,
+ 24→ FileText,
+ 25→ Folder,
+ 26→ FolderOpen,
+ 27→ FolderPlus,
+ 28→ Lock,
+ 29→ Pencil,
+ 30→ Play,
+ 31→ Trash2,
+ 32→ Upload,
+ 33→ User,
+ 34→} from 'lucide-react'
+ 35→import {
+ 36→ AlertDialog,
+ 37→ AlertDialogAction,
+ 38→ AlertDialogCancel,
+ 39→ AlertDialogContent,
+ 40→ AlertDialogDescription,
+ 41→ AlertDialogFooter,
+ 42→ AlertDialogHeader,
+ 43→ AlertDialogTitle,
+ 44→} from '@/components/ui/alert-dialog'
+ 45→import { Badge } from '@/components/ui/badge'
+ 46→import { Button } from '@/components/ui/button'
+ 47→import { Checkbox } from '@/components/ui/checkbox'
+ 48→import {
+ 49→ Dialog,
+ 50→ DialogContent,
+ 51→ DialogDescription,
+ 52→ DialogFooter,
+ 53→ DialogHeader,
+ 54→ DialogTitle,
+ 55→} from '@/components/ui/dialog'
+ 56→import { Input } from '@/components/ui/input'
+ 57→import { Label } from '@/components/ui/label'
+ 58→import { STARTER_TEMPLATE } from '@/lib/ava/presets'
+ 59→import { applyUserPresets, refreshPresets, useAvaStore, type PresetItem } from '@/lib/store'
+ 60→import { applyScriptText } from '@/components/ava/audio-source'
+ 61→import { thinScrollbar } from '@/components/ava/shared'
+ 62→import { useToast } from '@/hooks/use-toast'
+ 63→import { cn } from '@/lib/utils'
+ 64→import { ROOT, USER_ROOT, isEmbeddedPath, isUserAreaPath } from '@/lib/preset-paths'
+ 65→import {
+ 66→ createUserFolder,
+ 67→ createUserPreset,
+ 68→ deleteUserFolder,
+ 69→ deleteUserPreset,
+ 70→ presetIsLocal,
+ 71→ renameUserFolder,
+ 72→ renameUserPreset,
+ 73→} from '@/lib/ava/user-presets'
+ 74→import { exportPresetsZip, importPresetsZip } from '@/lib/ava/preset-io'
+ 75→
+ 76→/** Same default-expansion rule as the store: paths with ≤ 2 segments open. */
+ 77→function defaultOpen(path: string): boolean {
+ 78→ return path.split('/').length <= 2
+ 79→}
+ 80→
+ 81→function plural(n: number, word: string): string {
+ 82→ return `${n} ${word}${n === 1 ? '' : 's'}`
+ 83→}
+ 84→
+ 85→// ---------------------------------------------------------------------------
+ 86→// tree construction
+ 87→// ---------------------------------------------------------------------------
+ 88→
+ 89→interface FolderNode {
+ 90→ path: string
+ 91→ name: string
+ 92→ depth: number
+ 93→ children: FolderNode[]
+ 94→ presets: PresetItem[]
+ 95→ /** Presets in this folder and every folder below it. */
+ 96→ subtreeCount: number
+ 97→}
+ 98→
+ 99→/** Segment-wise alphabetical compare (a parent always sorts before its children). */
+ 100→function comparePaths(a: string, b: string): number {
+ 101→ const sa = a.split('/')
+ 102→ const sb = b.split('/')
+ 103→ const shared = Math.min(sa.length, sb.length)
+ 104→ for (let i = 0; i < shared; i++) {
+ 105→ const c = sa[i].toLowerCase().localeCompare(sb[i].toLowerCase())
+ 106→ if (c !== 0) return c
+ 107→ }
+ 108→ return sa.length - sb.length
+ 109→}
+ 110→
+ 111→/**
+ 112→ * Build the tree from folder rows + presets. Defensive: a preset whose
+ 113→ * folderPath has no Folder row still gets a node (and its ancestors).
+ 114→ */
+ 115→function buildTree(folders: string[], presets: PresetItem[]): FolderNode[] {
+ 116→ const pathSet = new Set(folders)
+ 117→ for (const p of presets) {
+ 118→ const segs = p.folderPath.split('/')
+ 119→ for (let i = 1; i <= segs.length; i++) pathSet.add(segs.slice(0, i).join('/'))
+ 120→ }
+ 121→ const paths = [...pathSet].sort(comparePaths)
+ 122→
+ 123→ const nodes = new Map()
+ 124→ for (const path of paths) {
+ 125→ const segs = path.split('/')
+ 126→ nodes.set(path, {
+ 127→ path,
+ 128→ name: segs[segs.length - 1] || path,
+ 129→ depth: segs.length - 1,
+ 130→ children: [],
+ 131→ presets: [],
+ 132→ subtreeCount: 0,
+ 133→ })
+ 134→ }
+ 135→ const roots: FolderNode[] = []
+ 136→ for (const path of paths) {
+ 137→ const node = nodes.get(path)
+ 138→ if (!node) continue
+ 139→ const slash = path.lastIndexOf('/')
+ 140→ const parent = slash >= 0 ? nodes.get(path.slice(0, slash)) : undefined
+ 141→ if (parent) parent.children.push(node)
+ 142→ else roots.push(node)
+ 143→ }
+ 144→ for (const p of presets) nodes.get(p.folderPath)?.presets.push(p)
+ 145→ for (const node of nodes.values()) {
+ 146→ node.presets.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()))
+ 147→ }
+ 148→ const countSubtree = (node: FolderNode): number => {
+ 149→ node.subtreeCount = node.presets.length
+ 150→ for (const child of node.children) node.subtreeCount += countSubtree(child)
+ 151→ return node.subtreeCount
+ 152→ }
+ 153→ roots.forEach(countSubtree)
+ 154→ return roots
+ 155→}
+ 156→
+ 157→// ---------------------------------------------------------------------------
+ 158→// shared load action (Load Preset button + tree double-click)
+ 159→// ---------------------------------------------------------------------------
+ 160→
+ 161→function useLoadPreset() {
+ 162→ const { toast } = useToast()
+ 163→ return useCallback(
+ 164→ (preset: PresetItem) => {
+ 165→ const result = applyScriptText(preset.content)
+ 166→ const st = useAvaStore.getState()
+ 167→ if (result.ok) {
+ 168→ st.setActivePresetId(preset.id)
+ 169→ st.setEditTarget({ id: preset.id, name: preset.name, folderPath: preset.folderPath })
+ 170→ // No "Preset loaded" toast (v7.3): the footer's SCRIPT: label
+ 171→ // already echoes the running preset — the popup was intrusive and
+ 172→ // only compile errors genuinely need the attention grab.
+ 173→ } else if (result.error) {
+ 174→ toast({
+ 175→ title: 'Preset failed to compile',
+ 176→ description: `[${result.error.section}] line ${result.error.line}: ${result.error.message}`,
+ 177→ variant: 'destructive',
+ 178→ })
+ 179→ st.setTab('script')
+ 180→ }
+ 181→ },
+ 182→ [toast],
+ 183→ )
+ 184→}
+ 185→
+ 186→// ---------------------------------------------------------------------------
+ 187→// tree rows
+ 188→// ---------------------------------------------------------------------------
+ 189→
+ 190→function TreeFolder({ node }: { node: FolderNode }) {
+ 191→ const isOpen = useAvaStore((s) => s.expandedFolders[node.path] ?? defaultOpen(node.path))
+ 192→ const selected = useAvaStore((s) => s.selectedFolder === node.path)
+ 193→ const readOnly = node.path === ROOT || isEmbeddedPath(node.path)
+ 194→
+ 195→ return (
+ 196→
+ 197→
+ 208→ useAvaStore.getState().toggleFolder(node.path)}
+ 211→ aria-label={isOpen ? `Collapse folder ${node.name}` : `Expand folder ${node.name}`}
+ 212→ className="flex size-9 shrink-0 items-center justify-center rounded-sm text-zinc-500 hover:text-zinc-200"
+ 213→ >
+ 214→
+ 218→
+ 219→ useAvaStore.getState().selectFolder(node.path)}
+ 222→ title={readOnly ? `${node.path} -- read-only (shipped with OVE)` : node.path}
+ 223→ className="flex h-9 min-w-0 flex-1 items-center gap-1.5 py-1 pr-1 text-left"
+ 224→ >
+ 225→ {isOpen ? (
+ 226→
+ 227→ ) : (
+ 228→
+ 229→ )}
+ 230→
+ 233→ {node.name}
+ 234→
+ 235→ {readOnly && (
+ 236→
+ 241→
+ 242→
+ 243→ )}
+ 244→
+ 249→ {node.subtreeCount}
+ 250→
+ 251→
+ 252→
+ 253→ {isOpen && (
+ 254→
+ 255→ {node.children.map((child) => (
+ 256→
+ 257→ ))}
+ 258→ {node.presets.map((p) => (
+ 259→
+ 260→ ))}
+ 261→ {node.children.length === 0 && node.presets.length === 0 && (
+ 262→
+ 269→ {node.path === USER_ROOT
+ 270→ ? '(Created and imported presets are stored in this browser.)'
+ 271→ : '(empty)'}
+ 272→
+ 273→ )}
+ 274→
+ 275→ )}
+ 276→
+ 277→ )
+ 278→}
+ 279→
+ 280→function TreePreset({ preset, depth }: { preset: PresetItem; depth: number }) {
+ 281→ const loadPreset = useLoadPreset()
+ 282→ const selected = useAvaStore((s) => s.selectedPresetId === preset.id)
+ 283→ const running = useAvaStore((s) => s.activePresetId === preset.id)
+ 284→ const own = presetIsLocal(preset.id)
+ 285→
+ 286→ return (
+ 287→
+ 301→
useAvaStore.getState().selectPreset(preset.id)}
+ 304→ onDoubleClick={() => loadPreset(preset)}
+ 305→ title={`${preset.name} (double-click to run)${own ? ' -- yours (this browser)' : ''}`}
+ 306→ className="flex h-9 min-w-0 flex-1 items-center gap-1.5 py-1 pr-1 text-left"
+ 307→ >
+ 308→ {running ? (
+ 309→
+ 310→ ) : (
+ 311→
+ 312→ )}
+ 313→
+ 314→ {preset.name}
+ 315→
+ 316→ {preset.approx && (
+ 317→
+ 321→ ≈
+ 322→
+ 323→ )}
+ 324→ {own && (
+ 325→
+ 330→
+ 331→
+ 332→ )}
+ 333→
+ 334→ {preset.originalFile && (
+ 335→
+ 342→
+ 343→
+ 344→ )}
+ 345→
+ 346→ )
+ 347→}
+ 348→
+ 349→// ---------------------------------------------------------------------------
+ 350→// small rename/create dialog shared by the three name-requiring actions
+ 351→// ---------------------------------------------------------------------------
+ 352→
+ 353→/**
+ 354→ * Fully controlled name prompt shared by the three name-requiring actions
+ 355→ * (create preset / new subfolder / rename). The parent owns the input value
+ 356→ * and error so each open starts clean without mount tricks.
+ 357→ */
+ 358→function NameDialog({
+ 359→ open,
+ 360→ onOpenChange,
+ 361→ title,
+ 362→ description,
+ 363→ placeholder,
+ 364→ value,
+ 365→ onChange,
+ 366→ error,
+ 367→ onError,
+ 368→ submitLabel,
+ 369→ busy,
+ 370→ onSubmit,
+ 371→}: {
+ 372→ open: boolean
+ 373→ onOpenChange: (open: boolean) => void
+ 374→ title: string
+ 375→ description: string
+ 376→ placeholder: string
+ 377→ value: string
+ 378→ onChange: (value: string) => void
+ 379→ error: string | null
+ 380→ onError: (error: string | null) => void
+ 381→ submitLabel: string
+ 382→ busy: boolean
+ 383→ /** Return true to close the dialog (errors keep it open). */
+ 384→ onSubmit: (name: string) => Promise
+ 385→}) {
+ 386→ const submit = async () => {
+ 387→ const trimmed = value.trim()
+ 388→ if (!trimmed) {
+ 389→ onError('Enter a name first.')
+ 390→ return
+ 391→ }
+ 392→ const ok = await onSubmit(trimmed)
+ 393→ if (ok) onOpenChange(false)
+ 394→ }
+ 395→
+ 396→ return (
+ 397→
+ 398→
+ 399→
+ 400→ {title}
+ 401→ {description}
+ 402→
+ 403→
+ 404→
onChange(e.target.value)}
+ 407→ placeholder={placeholder}
+ 408→ autoFocus
+ 409→ aria-label={title}
+ 410→ className="h-9 border-zinc-700 bg-zinc-900 font-mono text-xs"
+ 411→ onFocus={(e) => e.currentTarget.select()}
+ 412→ onKeyDown={(e) => {
+ 413→ if (e.key === 'Enter') void submit()
+ 414→ }}
+ 415→ />
+ 416→ {error &&
{error}
}
+ 417→
+ 418→
+ 419→ onOpenChange(false)}>
+ 420→ Cancel
+ 421→
+ 422→ void submit()}
+ 426→ >
+ 427→ {busy ? 'Working…' : submitLabel}
+ 428→
+ 429→
+ 430→
+ 431→
+ 432→ )
+ 433→}
+ 434→
+ 435→// ---------------------------------------------------------------------------
+ 436→// the tab
+ 437→// ---------------------------------------------------------------------------
+ 438→
+ 439→type DialogKind = 'createPreset' | 'newFolder' | 'rename' | null
+ 440→
+ 441→interface DeleteTarget {
+ 442→ kind: 'folder' | 'preset'
+ 443→ name: string
+ 444→ /** Folder path (folder delete) or preset id (preset delete). */
+ 445→ path: string
+ 446→ presetCount: number
+ 447→ folderCount: number
+ 448→}
+ 449→
+ 450→export function PresetsTab() {
+ 451→ const { toast } = useToast()
+ 452→ const loadPreset = useLoadPreset()
+ 453→
+ 454→ const presets = useAvaStore((s) => s.presets)
+ 455→ const folders = useAvaStore((s) => s.folders)
+ 456→ const selectedFolder = useAvaStore((s) => s.selectedFolder)
+ 457→ const selectedPresetId = useAvaStore((s) => s.selectedPresetId)
+ 458→ const randomize = useAvaStore((s) => s.randomize)
+ 459→ const sequenceMode = useAvaStore((s) => s.sequenceMode)
+ 460→ const randomIntervalSec = useAvaStore((s) => s.randomIntervalSec)
+ 461→ const randomScope = useAvaStore((s) => s.randomScope)
+ 462→
+ 463→ const [dialog, setDialog] = useState(null)
+ 464→ const [nameValue, setNameValue] = useState('')
+ 465→ const [nameError, setNameError] = useState(null)
+ 466→ const [busy, setBusy] = useState(false)
+ 467→ const [deleteTarget, setDeleteTarget] = useState(null)
+ 468→ const [delayText, setDelayText] = useState(String(randomIntervalSec))
+ 469→ const [delayError, setDelayError] = useState(false)
+ 470→ const importInputRef = useRef(null)
+ 471→
+ 472→ // freshness refresh whenever the tab mounts (initial merge happens on app load)
+ 473→ useEffect(() => {
+ 474→ void refreshPresets()
+ 475→ }, [])
+ 476→
+ 477→ // keep the delay input in sync when the store value changes elsewhere
+ 478→ useEffect(() => {
+ 479→ setDelayText(String(randomIntervalSec))
+ 480→ }, [randomIntervalSec])
+ 481→
+ 482→ const tree = useMemo(() => buildTree(folders, presets), [folders, presets])
+ 483→ const selectedPreset = useMemo(
+ 484→ () => presets.find((p) => p.id === selectedPresetId) ?? null,
+ 485→ [presets, selectedPresetId],
+ 486→ )
+ 487→
+ 488→ // ---- read-only vs. browser-owned areas --------------------------------
+ 489→ const folderReadOnly = selectedFolder === ROOT || isEmbeddedPath(selectedFolder)
+ 490→ const folderProtected = selectedFolder === USER_ROOT // the user area root itself
+ 491→ const presetOwn = selectedPreset ? presetIsLocal(selectedPreset.id) : false
+ 492→
+ 493→ const folderName = selectedFolder.split('/').pop() ?? selectedFolder
+ 494→ const folderPrefix = `${selectedFolder}/`
+ 495→ const folderPresetCount = presets.filter(
+ 496→ (p) => p.folderPath === selectedFolder || p.folderPath.startsWith(folderPrefix),
+ 497→ ).length
+ 498→ const childFolderCount = folders.filter(
+ 499→ (f) => f.startsWith(folderPrefix) && !f.slice(folderPrefix.length).includes('/'),
+ 500→ ).length
+ 501→ const scopeCount = presets.filter(
+ 502→ (p) => randomScope === 'root' || p.folderPath === randomScope || p.folderPath.startsWith(`${randomScope}/`),
+ 503→ ).length
+ 504→ const scopeLabel = randomScope === 'root' ? 'all presets' : (randomScope.split('/').pop() ?? randomScope)
+ 505→
+ 506→ // -----------------------------------------------------------------
+ 507→ // actions — all user-area CRUD happens in THIS browser (localStorage)
+ 508→ // -----------------------------------------------------------------
+ 509→
+ 510→ const submitCreatePreset = async (name: string): Promise => {
+ 511→ setBusy(true)
+ 512→ try {
+ 513→ const res = createUserPreset({
+ 514→ name,
+ 515→ content: STARTER_TEMPLATE,
+ 516→ folderPath: selectedFolder,
+ 517→ author: 'you',
+ 518→ })
+ 519→ if (!res.ok) {
+ 520→ toast({ title: 'Preset not created', description: res.error, variant: 'destructive' })
+ 521→ return false
+ 522→ }
+ 523→ applyUserPresets()
+ 524→ const st = useAvaStore.getState()
+ 525→ st.setEditTarget({ id: res.preset.id, name: res.preset.name, folderPath: res.preset.folderPath })
+ 526→ st.setScriptText(STARTER_TEMPLATE)
+ 527→ applyScriptText(STARTER_TEMPLATE)
+ 528→ st.setActivePresetId(res.preset.id)
+ 529→ st.selectPreset(res.preset.id)
+ 530→ st.setTab('script')
+ 531→ toast({
+ 532→ title: 'Preset created',
+ 533→ description: `"${res.preset.name}" created in this browser -- now edit and Save.`,
+ 534→ })
+ 535→ return true
+ 536→ } finally {
+ 537→ setBusy(false)
+ 538→ }
+ 539→ }
+ 540→
+ 541→ const submitNewFolder = async (name: string): Promise => {
+ 542→ const path = `${selectedFolder}/${name}`
+ 543→ setBusy(true)
+ 544→ try {
+ 545→ const res = createUserFolder(path)
+ 546→ if (!res.ok) {
+ 547→ toast({ title: 'Folder not created', description: res.error, variant: 'destructive' })
+ 548→ return false
+ 549→ }
+ 550→ applyUserPresets()
+ 551→ const st = useAvaStore.getState()
+ 552→ st.toggleFolder(selectedFolder, true)
+ 553→ st.selectFolder(res.path)
+ 554→ toast({ title: 'Folder created', description: `"${name}" added under ${selectedFolder} (this browser).` })
+ 555→ return true
+ 556→ } finally {
+ 557→ setBusy(false)
+ 558→ }
+ 559→ }
+ 560→
+ 561→ const submitRename = async (name: string): Promise => {
+ 562→ setBusy(true)
+ 563→ try {
+ 564→ if (selectedPreset) {
+ 565→ const res = renameUserPreset(selectedPreset.id, name)
+ 566→ if (!res.ok) {
+ 567→ toast({ title: 'Rename failed', description: res.error, variant: 'destructive' })
+ 568→ return false
+ 569→ }
+ 570→ applyUserPresets()
+ 571→ const st = useAvaStore.getState()
+ 572→ if (st.editTarget?.id === selectedPreset.id) {
+ 573→ st.setEditTarget({
+ 574→ id: res.preset.id,
+ 575→ name: res.preset.name,
+ 576→ folderPath: res.preset.folderPath,
+ 577→ })
+ 578→ }
+ 579→ toast({
+ 580→ title: 'Preset renamed',
+ 581→ description: `"${selectedPreset.name}" is now "${res.preset.name}".`,
+ 582→ })
+ 583→ return true
+ 584→ }
+ 585→ // folder rename — everything below moves with it (browser-side)
+ 586→ const res = renameUserFolder(selectedFolder, name)
+ 587→ if (!res.ok) {
+ 588→ toast({ title: 'Rename failed', description: res.error, variant: 'destructive' })
+ 589→ return false
+ 590→ }
+ 591→ applyUserPresets()
+ 592→ const st = useAvaStore.getState()
+ 593→ st.selectFolder(res.to)
+ 594→ if (
+ 595→ st.editTarget &&
+ 596→ (st.editTarget.folderPath === selectedFolder || st.editTarget.folderPath.startsWith(folderPrefix))
+ 597→ ) {
+ 598→ st.setEditTarget({
+ 599→ ...st.editTarget,
+ 600→ folderPath: res.to + st.editTarget.folderPath.slice(selectedFolder.length),
+ 601→ })
+ 602→ }
+ 603→ toast({ title: 'Folder renamed', description: `Now at ${res.to}.` })
+ 604→ return true
+ 605→ } finally {
+ 606→ setBusy(false)
+ 607→ }
+ 608→ }
+ 609→
+ 610→ const openDialog = (kind: Exclude, initialName: string) => {
+ 611→ setDialog(kind)
+ 612→ setNameValue(initialName)
+ 613→ setNameError(null)
+ 614→ }
+ 615→
+ 616→ const openPresetDelete = () => {
+ 617→ if (!selectedPreset) return
+ 618→ setDeleteTarget({
+ 619→ kind: 'preset',
+ 620→ name: selectedPreset.name,
+ 621→ path: selectedPreset.id,
+ 622→ presetCount: 1,
+ 623→ folderCount: 0,
+ 624→ })
+ 625→ }
+ 626→
+ 627→ const openFolderDelete = () => {
+ 628→ const presetCount = presets.filter(
+ 629→ (p) => p.folderPath === selectedFolder || p.folderPath.startsWith(folderPrefix),
+ 630→ ).length
+ 631→ const folderCount = folders.filter((f) => f.startsWith(folderPrefix)).length
+ 632→ setDeleteTarget({ kind: 'folder', name: folderName, path: selectedFolder, presetCount, folderCount })
+ 633→ }
+ 634→
+ 635→ const confirmDelete = async () => {
+ 636→ if (!deleteTarget || busy) return
+ 637→ setBusy(true)
+ 638→ try {
+ 639→ const isFolder = deleteTarget.kind === 'folder'
+ 640→ const res = isFolder ? deleteUserFolder(deleteTarget.path) : deleteUserPreset(deleteTarget.path)
+ 641→ if (!res.ok) {
+ 642→ toast({ title: 'Delete failed', description: res.error, variant: 'destructive' })
+ 643→ return
+ 644→ }
+ 645→ applyUserPresets()
+ 646→ toast({
+ 647→ title: isFolder ? 'Folder deleted' : 'Preset deleted',
+ 648→ description: isFolder
+ 649→ ? `"${deleteTarget.name}" and everything inside it removed from this browser.`
+ 650→ : `"${deleteTarget.name}" removed from this browser.`,
+ 651→ })
+ 652→ } finally {
+ 653→ setBusy(false)
+ 654→ setDeleteTarget(null)
+ 655→ }
+ 656→ }
+ 657→
+ 658→ /** Copy an embedded preset into this visitor's browser library. */
+ 659→ const copyToMyPresets = (preset: PresetItem) => {
+ 660→ const candidates = [preset.name, `${preset.name} (copy)`, `${preset.name} (copy 2)`, `${preset.name} (copy 3)`]
+ 661→ for (const name of candidates) {
+ 662→ const res = createUserPreset({
+ 663→ name,
+ 664→ content: preset.content,
+ 665→ folderPath: USER_ROOT,
+ 666→ author: preset.author,
+ 667→ description: preset.description,
+ 668→ })
+ 669→ if (res.ok) {
+ 670→ applyUserPresets()
+ 671→ const st = useAvaStore.getState()
+ 672→ st.toggleFolder(USER_ROOT, true)
+ 673→ st.selectPreset(res.preset.id)
+ 674→ toast({
+ 675→ title: 'Copied to your presets',
+ 676→ description: `"${res.preset.name}" is now yours to edit -- stored in this browser only.`,
+ 677→ })
+ 678→ return
+ 679→ }
+ 680→ if (!res.error.includes('already exists')) {
+ 681→ toast({ title: 'Copy failed', description: res.error, variant: 'destructive' })
+ 682→ return
+ 683→ }
+ 684→ }
+ 685→ toast({
+ 686→ title: 'Copy failed',
+ 687→ description: 'A preset with that name already exists -- rename it in "user created" first.',
+ 688→ variant: 'destructive',
+ 689→ })
+ 690→ }
+ 691→
+ 692→ const exportZip = async () => {
+ 693→ if (busy) return
+ 694→ setBusy(true)
+ 695→ try {
+ 696→ const res = await exportPresetsZip(selectedFolder, presets, folders)
+ 697→ if (res.ok) {
+ 698→ toast({ title: 'Export ready', description: `${plural(res.presets, 'preset')} zipped and downloading.` })
+ 699→ } else {
+ 700→ toast({ title: 'Export failed', description: res.error, variant: 'destructive' })
+ 701→ }
+ 702→ } finally {
+ 703→ setBusy(false)
+ 704→ }
+ 705→ }
+ 706→
+ 707→ const importZip = async (file: File) => {
+ 708→ setBusy(true)
+ 709→ try {
+ 710→ const res = await importPresetsZip(file, selectedFolder)
+ 711→ if (!res.ok) {
+ 712→ toast({ title: 'Import failed', description: res.error, variant: 'destructive' })
+ 713→ return
+ 714→ }
+ 715→ applyUserPresets()
+ 716→ useAvaStore.getState().toggleFolder(USER_ROOT, true)
+ 717→ let summary = `Imported ${res.imported} presets into your browser library`
+ 718→ if ((res.skipped ?? 0) > 0) summary += `, ${res.skipped} duplicates skipped`
+ 719→ if ((res.foldersCreated ?? 0) > 0) summary += ` -- ${res.foldersCreated} folders created`
+ 720→ toast({ title: 'Import complete', description: `${summary}.` })
+ 721→ } catch {
+ 722→ toast({ title: 'Import failed', description: 'Could not read that zip.', variant: 'destructive' })
+ 723→ } finally {
+ 724→ setBusy(false)
+ 725→ }
+ 726→ }
+ 727→
+ 728→ // -----------------------------------------------------------------
+ 729→ // randomizer controls (timer runs in the store)
+ 730→ // -----------------------------------------------------------------
+ 731→
+ 732→ const onDelayChange = (raw: string) => {
+ 733→ setDelayText(raw)
+ 734→ const result = useAvaStore.getState().setRandomIntervalSec(Number(raw))
+ 735→ setDelayError(!result.ok)
+ 736→ if (result.ok && result.value !== undefined) setDelayText(String(result.value))
+ 737→ }
+ 738→
+ 739→ const onDelayBlur = () => {
+ 740→ // invalid / partial input never persists — revert to the committed value
+ 741→ if (delayError) setDelayText(String(randomIntervalSec))
+ 742→ }
+ 743→
+ 744→ // -----------------------------------------------------------------
+ 745→ // render
+ 746→ // -----------------------------------------------------------------
+ 747→
+ 748→ return (
+ 749→
+ 750→
+ 751→
+ 752→ {presets.length} presets ·{' '}
+ 753→ {folders.length} folders
+ 754→
+ 755→
double-click a preset to run it
+ 756→
+ 757→
+ 758→
+ 759→ {/* LEFT -- folder/file tree */}
+ 760→
+ 761→
+ 766→ {tree.map((node) => (
+ 767→
+ 768→ ))}
+ 769→ {presets.length === 0 && (
+ 770→
Loading presets…
+ 771→ )}
+ 772→
+ 773→
+ 774→
+ 775→ {/* RIGHT -- management panel */}
+ 776→
+ 777→
+ 778→ {selectedPreset ? (
+ 779→
+ 780→ ) : (
+ 781→
+ 782→ )}
+ 783→
+ 784→
+ 788→ {selectedPreset ? selectedPreset.name : folderName}
+ 789→
+ 790→
+ 794→ {selectedPreset ? selectedPreset.folderPath : selectedFolder}
+ 795→
+ 796→ {!selectedPreset && (
+ 797→
+ 798→ {plural(folderPresetCount, 'preset')} · {plural(childFolderCount, 'subfolder')}
+ 799→
+ 800→ )}
+ 801→
+ 802→ {(folderReadOnly || (selectedPreset && !presetOwn)) && (
+ 803→
+ 807→ read-only
+ 808→
+ 809→ )}
+ 810→
+ 811→
+ 812→
+ 813→ {selectedPreset ? (
+ 814→ <>
+ 815→
loadPreset(selectedPreset)}
+ 818→ className="h-9 w-full gap-1.5 bg-emerald-400 text-emerald-950 hover:bg-emerald-300"
+ 819→ title="Run this preset on the engine and open it in the editor"
+ 820→ >
+ 821→ Load Preset
+ 822→
+ 823→ {presetOwn ? (
+ 824→
+ 825→
openDialog('rename', selectedPreset.name)}
+ 829→ title="Rename this preset (within its folder)"
+ 830→ className="h-8 shrink-0 gap-1 whitespace-nowrap text-xs has-[>svg]:px-2 border-zinc-700"
+ 831→ >
+ 832→ Rename
+ 833→
+ 834→
+ 841→ Delete
+ 842→
+ 843→
+ 844→ ) : (
+ 845→
copyToMyPresets(selectedPreset)}
+ 849→ title="Save an editable copy of this preset into your browser's 'user created' folder"
+ 850→ className="h-9 w-full gap-1.5 border-zinc-700"
+ 851→ >
+ 852→ Copy to my presets
+ 853→
+ 854→ )}
+ 855→ >
+ 856→ ) : folderReadOnly ? (
+ 857→ <>
+ 858→
+ 859→ void exportZip()}
+ 864→ title="Download this folder (with subfolders) as a .zip preserving the tree -- built in your browser"
+ 865→ className="h-8 shrink-0 gap-1 whitespace-nowrap text-xs has-[>svg]:px-2 border-zinc-700"
+ 866→ >
+ 867→ Export .zip
+ 868→
+ 869→ importInputRef.current?.click()}
+ 874→ title="Import a .zip of .ava presets -- they land in your browser's 'user created' folder"
+ 875→ className="h-8 shrink-0 gap-1 whitespace-nowrap text-xs has-[>svg]:px-2 border-zinc-700"
+ 876→ >
+ 877→ Import .zip
+ 878→
+ 879→
+ 880→ >
+ 881→ ) : (
+ 882→ <>
+ 883→
openDialog('createPreset', '')}
+ 887→ title={`Create a new preset from the starter template inside ${selectedFolder} (this browser)`}
+ 888→ className="h-9 w-full gap-1.5 border-zinc-700"
+ 889→ >
+ 890→ Create Preset
+ 891→
+ 892→
openDialog('newFolder', '')}
+ 896→ title={`Create a subfolder inside ${selectedFolder} (this browser)`}
+ 897→ className="h-9 w-full gap-1.5 border-zinc-700"
+ 898→ >
+ 899→ New Subfolder
+ 900→
+ 901→
+ 902→
openDialog('rename', folderName)}
+ 907→ title={folderProtected ? '"user created" is the root of your library' : `Rename ${folderName} -- everything inside moves with it`}
+ 908→ className="h-8 shrink-0 gap-1 whitespace-nowrap text-xs has-[>svg]:px-2 border-zinc-700"
+ 909→ >
+ 910→ Rename
+ 911→
+ 912→
+ 920→ Delete
+ 921→
+ 922→
+ 923→
+ 924→ void exportZip()}
+ 929→ title="Download this folder (with subfolders) as a .zip preserving the tree -- built in your browser"
+ 930→ className="h-8 shrink-0 gap-1 whitespace-nowrap text-xs has-[>svg]:px-2 border-zinc-700"
+ 931→ >
+ 932→ Export .zip
+ 933→
+ 934→ importInputRef.current?.click()}
+ 939→ title={`Import a .zip of .ava presets into ${selectedFolder} -- the tree inside the zip is preserved`}
+ 940→ className="h-8 shrink-0 gap-1 whitespace-nowrap text-xs has-[>svg]:px-2 border-zinc-700"
+ 941→ >
+ 942→ Import .zip
+ 943→
+ 944→
+ 945→ >
+ 946→ )}
+ 947→
+ 948→
+ 949→ {/* preset mixing (club mode) -- Random or Sequence, one timer in the store */}
+ 950→
+ 951→
+ 952→
+ 953→ useAvaStore.getState().setRandomize(v === true)}
+ 957→ aria-label="Random presets"
+ 958→ className="size-4 data-[state=checked]:border-emerald-400 data-[state=checked]:bg-emerald-400 data-[state=checked]:text-emerald-950"
+ 959→ />
+ 960→
+ 961→ Random
+ 962→
+ 963→
+ 964→
+ 965→ useAvaStore.getState().setSequenceMode(v === true)}
+ 969→ aria-label="Sequence presets"
+ 970→ className="size-4 data-[state=checked]:border-emerald-400 data-[state=checked]:bg-emerald-400 data-[state=checked]:text-emerald-950"
+ 971→ />
+ 972→
+ 973→ Sequence
+ 974→
+ 975→
+ 976→
+ 977→ onDelayChange(e.target.value)}
+ 980→ onBlur={onDelayBlur}
+ 981→ inputMode="numeric"
+ 982→ aria-label="Random switch delay in seconds"
+ 983→ className="h-8 w-16 border-zinc-700 bg-zinc-900 px-2 text-center font-mono text-xs tabular-nums"
+ 984→ />
+ 985→ sec
+ 986→
+ 987→
+ 988→ {delayError && (
+ 989→
Delay must be between 3 and 60 seconds.
+ 990→ )}
+ 991→
+ 992→ {sequenceMode ? 'Sequence from:' : 'Random from:'}{' '}
+ 993→ {scopeLabel}
+ 994→ {scopeCount === 0 && -- no presets in this folder yet }
+ 995→
+ 996→ {(randomize || sequenceMode) && (
+ 997→
+ 998→
+ 999→
+ 1000→ {sequenceMode ? 'SEQUENCE' : 'RANDOM'} · every {randomIntervalSec}s
+ 1001→
+ 1002→
+ 1003→ )}
+ 1004→
+ 1005→
+ 1006→
+ 1007→
+ 1008→ {/* name dialogs */}
+ 1009→
setDialog(o ? 'createPreset' : null)}
+ 1012→ title="Create preset"
+ 1013→ description={`New preset from the starter template inside ${selectedFolder} -- saved in this browser.`}
+ 1014→ placeholder="Preset name"
+ 1015→ value={nameValue}
+ 1016→ onChange={setNameValue}
+ 1017→ error={nameError}
+ 1018→ onError={setNameError}
+ 1019→ submitLabel="Create"
+ 1020→ busy={busy}
+ 1021→ onSubmit={submitCreatePreset}
+ 1022→ />
+ 1023→ setDialog(o ? 'newFolder' : null)}
+ 1026→ title="New subfolder"
+ 1027→ description={`Created inside ${selectedFolder} -- saved in this browser.`}
+ 1028→ placeholder="Folder name"
+ 1029→ value={nameValue}
+ 1030→ onChange={setNameValue}
+ 1031→ error={nameError}
+ 1032→ onError={setNameError}
+ 1033→ submitLabel="Create"
+ 1034→ busy={busy}
+ 1035→ onSubmit={submitNewFolder}
+ 1036→ />
+ 1037→ setDialog(o ? 'rename' : null)}
+ 1040→ title={selectedPreset ? 'Rename preset' : 'Rename folder'}
+ 1041→ description={
+ 1042→ selectedPreset
+ 1043→ ? `Currently "${selectedPreset.name}" in ${selectedPreset.folderPath}.`
+ 1044→ : `Currently "${folderName}" -- everything inside moves with it.`
+ 1045→ }
+ 1046→ placeholder="New name"
+ 1047→ value={nameValue}
+ 1048→ onChange={setNameValue}
+ 1049→ error={nameError}
+ 1050→ onError={setNameError}
+ 1051→ submitLabel="Rename"
+ 1052→ busy={busy}
+ 1053→ onSubmit={submitRename}
+ 1054→ />
+ 1055→
+ 1056→ {/* delete confirmation */}
+ 1057→ !o && setDeleteTarget(null)}>
+ 1058→
+ 1059→
+ 1060→
+ 1061→ {deleteTarget?.kind === 'folder'
+ 1062→ ? `Delete "${deleteTarget.name}"?`
+ 1063→ : `Delete preset "${deleteTarget?.name ?? ''}"?`}
+ 1064→
+ 1065→
+ 1066→ {deleteTarget?.kind === 'folder'
+ 1067→ ? deleteTarget.presetCount > 0 || deleteTarget.folderCount > 0
+ 1068→ ? `Delete "${deleteTarget.name}" and everything inside it? This removes ${plural(deleteTarget.presetCount, 'preset')}${deleteTarget.folderCount > 0 ? ` in ${plural(deleteTarget.folderCount, 'folder')}` : ''} from this browser. This cannot be undone.`
+ 1069→ : `Delete "${deleteTarget.name}"? The folder is empty. This cannot be undone.`
+ 1070→ : `Delete preset "${deleteTarget?.name ?? ''}" from this browser? This cannot be undone.`}
+ 1071→
+ 1072→
+ 1073→
+ 1074→ Cancel
+ 1075→ {
+ 1078→ e.preventDefault()
+ 1079→ void confirmDelete()
+ 1080→ }}
+ 1081→ className="h-9 bg-rose-500 text-zinc-50 hover:bg-rose-400"
+ 1082→ >
+ 1083→ {busy ? 'Deleting…' : 'Delete'}
+ 1084→
+ 1085→
+ 1086→
+ 1087→
+ 1088→
+ 1089→ {/* hidden zip import picker */}
+ 1090→ {
+ 1098→ const file = e.target.files?.[0]
+ 1099→ if (file) void importZip(file)
+ 1100→ e.target.value = ''
+ 1101→ }}
+ 1102→ />
+ 1103→
+ 1104→ )
+ 1105→}
+ 1106→
\ No newline at end of file
diff --git a/tool-results/read_1793835734103_a615e8778496.txt b/tool-results/read_1793835734103_a615e8778496.txt
new file mode 100644
index 0000000..cb6cf6b
--- /dev/null
+++ b/tool-results/read_1793835734103_a615e8778496.txt
@@ -0,0 +1,1320 @@
+ 1→// AVA — embedded preset library (AUTO-GENERATED by tools/convert-avs.ts — do not edit)
+ 2→//
+ 3→// 90 Winamp AVS presets from the user's Google Drive collection,
+ 4→// auto-ported to the AVA script dialect by the converter. Ported presets run
+ 5→// the original SuperScope / Texer II code (machine-translated); approx
+ 6→// presets are UNIQUE generated stand-ins (16 families × seeded parameters)
+ 7→// for originals that rely on effects the script engine cannot run.
+ 8→//
+ 9→// The "Zai-Magic" folder at the END of EMBEDDED_LIBRARY is the exception to
+ 10→// "do not edit": ten presets HAND-AUTHORED for this engine (v7.00) to show
+ 11→// off beat-synced color — they use hsvr/hsvg/hsvb, beatage, megabuf particle
+ 12→// physics, getspec() and loop(). If the converter ever regenerates this
+ 13→// file, keep that section verbatim.
+ 14→//
+ 15→// EMBEDDED_SEED_VERSION: bump whenever this library is regenerated — the
+ 16→// GET /api/presets route re-seeds the root/embedded subtree when it changes
+ 17→// (v7.10: em dashes in descriptions/script comments replaced with '--').
+ 18→
+ 19→export const EMBEDDED_SEED_VERSION = '7.10'
+ 20→
+ 21→export interface EmbeddedPreset {
+ 22→ folder: string
+ 23→ name: string
+ 24→ author: string
+ 25→ description: string
+ 26→ originalFile?: string
+ 27→ content: string
+ 28→ /** true = generated stand-in (original relies on unportable effects). */
+ 29→ approx?: boolean
+ 30→}
+ 31→
+ 32→export const EMBEDDED_LIBRARY: EmbeddedPreset[] = [
+ 33→ {
+ 34→ folder: "root/embedded/Amp Picks",
+ 35→ name: "Life Is Violated",
+ 36→ author: "D&L",
+ 37→ description: "Auto-ported from the Winamp AVS preset \"D&L - Life Is Violated.avs\" by D&L. Original superscope code, machine-translated to the AVA script dialect.",
+ 38→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/D&L - Life Is Violated.avs",
+ 39→ approx: false,
+ 40→ content: "# Auto-ported from \"D&L - Life Is Violated.avs\" (Winamp AVS / D&L)\n# SuperScope point program, 4 AVSTrans macros expanded.\nname: \"Life Is Violated\"\nauthor: \"D&L\"\nmode: lines\nblend: additive\npersist: 0.88\nlinewidth: 2\nysign: -1\n\n[init]\nn=800;xa=-0.5;ya=0.0;xb=-0.0;yb=0.75;c=200;f=0;\r\nnxa=(rnd(100)-50)*.02;nya=(rnd(100)-50)*.02;\r\nnxb=(rnd(100)-50)*.02;nyb=(rnd(100)-50)*.02;\n\n[frame]\nf=f+1;\r\nt=1-((cos((f*3.1415)/c)+1)*.5);\r\nxa=((nxa-lxa)*t)+lxa;\r\nya=((nya-lya)*t)+lya;\r\nxb=((nxb-lxb)*t)+lxb;\r\nyb=((nyb-lyb)*t)+lyb;\r\nex=(xb-xa);\r\ney=(yb-ya);\r\nd=sqrt(sqr(ex)+sqr(ey));\r\nr=atan(ey/ex)+(3.1415/2);\r\ndv=d*2\n\n[beat]\nc=f;\r\nf=0;\r\nlxa=nxa;\r\nlya=nya;\r\nlxb=nxb;\r\nlyb=nyb;\r\nnxa=(rnd(100)-50)*.02;\r\nnya=(rnd(100)-50)*.02;\r\nnxb=(rnd(100)-50)*.02;\r\nnyb=(rnd(100)-50)*.02\n\n[point]\n//primary render\r\nx=(ex*i)+xa;\r\ny=(ey*i)+ya;\r\n\r\n//volume offset\r\nx=x+ ( cos(r) * v * dv);\r\ny=y+ ( sin(r) * v * dv);\r\n\r\n//color values\r\nred=i;\r\ngreen=(1-i);\r\nblue=abs(v*6);\n",
+ 41→ },
+ 42→ {
+ 43→ folder: "root/embedded/Amp Picks",
+ 44→ name: "Retrowall (Yathosho's Supermicrocosm)",
+ 45→ author: "Degnic",
+ 46→ description: "Auto-ported from the Winamp AVS preset \"Degnic - Retrowall (Yathosho's Supermicrocosm).avs\" by Degnic. The original is driven by DynamicMovement, Movement, Invert, which the script engine cannot run -- replaced with a generated \"orbits\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 47→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/Degnic - Retrowall (Yathosho's Supermicrocosm).avs",
+ 48→ approx: true,
+ 49→ content: "name: \"Retrowall (Yathosho's Supermicrocosm)\"\nmode: dots\nblend: additive\npersist: 0.843\nlinewidth: 2.178\n\n[init]\nrings = 7\nn = rings * 103\n\n[frame]\nt = t + dt * (0.344 + bass_att * 0.6)\nhue = hue + dt * 0.112\nsquash = 0.315 + 0.25 * sin(t * 0.205)\n\n[point]\nring = floor(i * rings)\nri3 = frac(i * rings)\nr0 = 0.16 + ring * 0.142\na = ri3 * 6.28318 + t * (1.2 - ring * 0.18) + ring * 2.4\nx = sin(a) * r0 * (1 + bass * 0.18) / aspect\ny = cos(a) * r0 * squash\nsat = frac(ri3 * 5)\nsize = if(below(sat, 0.14), 3 + bass * 3, 1)\nred = 0.25 + bass * 0.5 + ring * 0.07 * (0.45 + 0.55 * sin(hue * 6.28318 + ring * 1.3))\ngreen = 0.25 + bass * 0.5 + ring * 0.07 * (0.45 + 0.55 * sin(hue * 6.28318 + ring * 1.3 + 2.09))\nblue = 0.25 + bass * 0.5 + ring * 0.07 * (0.45 + 0.55 * sin(hue * 6.28318 + ring * 1.3 + 4.19))\n",
+ 50→ },
+ 51→ {
+ 52→ folder: "root/embedded/Amp Picks",
+ 53→ name: "Adrealine rush",
+ 54→ author: "Degnic",
+ 55→ description: "Auto-ported from the Winamp AVS preset \"Degnic-Adrealine rush.avs\" by Degnic. The original is driven by en03: Convolution Filter, Simple, Color Map, which the script engine cannot run -- replaced with a generated \"moire\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 56→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/Degnic-Adrealine rush.avs",
+ 57→ approx: true,
+ 58→ content: "name: \"Adrealine rush\"\nmode: dots\nblend: additive\npersist: 0.826\nlinewidth: 1.803\n\n[init]\nn = 1287\n\n[frame]\nt = t + dt * 0.304\nhue = hue + dt * 0.11\n\n[point]\nu = rand(i * 1.7) * 2 - 1\nw = rand(i * 3.3 + 11) * 2 - 1\nm = sin((u * 2.681 + w * 5.047 + t) * 3.14159) * sin((u * 6.682 - w * 2.465 - t * 0.7) * 3.14159)\nx = u * 0.907 / aspect\ny = w * 0.875\nlum = max(0, m) * (0.3 + bass * 0.9) + 0.06\nred = lum * 0.3\ngreen = lum * 0.65\nblue = min(1, lum * 1.3)\nsize = 1 + abs(m) * 3.025\n",
+ 59→ },
+ 60→ {
+ 61→ folder: "root/embedded/Amp Picks",
+ 62→ name: "Alien Heart",
+ 63→ author: "Duo",
+ 64→ description: "Auto-ported from the Winamp AVS preset \"Duo - Alien Heart.avs\" by Duo. The original is driven by Fadeout, ClearScreen, MovingParticle, which the script engine cannot run -- replaced with a generated \"tunnel\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 65→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/Duo - Alien Heart.avs",
+ 66→ approx: true,
+ 67→ content: "name: \"Alien Heart\"\nmode: lines\nblend: additive\npersist: 0.897\nlinewidth: 1.95\n\n[init]\nn = 1015\n\n[frame]\nt = t + dt * (0.377 + bass_att * 1.5)\nhue = hue + dt * 0.038\n\n[point]\nring = floor(i * 29)\nri2 = frac(i * 29)\nz = frac(ring / 29 + t)\nrad = 0.05 + z * 0.917\na = ri2 * 6.28318 + ring * 0.423 + sin(t * 0.7) * 2\nx = sin(a) * rad * (0.55 + sv * 0.656) / aspect\ny = cos(a) * rad * (0.55 + sv * 0.656)\nred = (1 - z) * (0.3 + bass * 0.7) * abs(sin(z * 4.256))\ngreen = (1 - z) * (0.3 + bass * 0.7) * 0.22\nblue = (1 - z) * (0.3 + bass * 0.7) * abs(cos(z * 4.256))\n",
+ 68→ },
+ 69→ {
+ 70→ folder: "root/embedded/Amp Picks",
+ 71→ name: "Brainstorm",
+ 72→ author: "Duo",
+ 73→ description: "Auto-ported from the Winamp AVS preset \"Duo - Brainstorm.avs\" by Duo. The original is driven by Invert, UniqueTone, Brightness, which the script engine cannot run -- replaced with a generated \"kaleido\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 74→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/Duo - Brainstorm.avs",
+ 75→ approx: true,
+ 76→ content: "name: \"Brainstorm\"\nmode: lines\nblend: additive\npersist: 0.859\nlinewidth: 2.309\n\n[init]\nsegs = 6\nn = 584\n\n[frame]\nrot = rot + dt * (0.093 + mid_att * 0.5)\nhue = hue + dt * 0.084\nflash = flash * 0.85\n\n[beat]\nflash = 1\n\n[point]\nseg = floor(i * segs)\nsi = frac(i * segs)\na0 = si * 6.28318 / segs + rot\namp = 0.1 + sv * 0.55 + bass * 0.25 + flash * 0.06\nrad = 0.15 + amp * (0.4 + 0.6 * sin(a0 * 9 + time * 2))\na = seg * 6.28318 / segs + a0 * if(below(seg % 2, 1), 1, -1)\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = 0.3 + amp * 1.3 * 0.85\ngreen = 0.3 + amp * 1.3 * 0.92\nblue = 0.3 + amp * 1.3\nskip = if(above(si, 0.96), 1, 0)\n",
+ 77→ },
+ 78→ {
+ 79→ folder: "root/embedded/Amp Picks",
+ 80→ name: "Molecules (S_KuPeRS Remix)",
+ 81→ author: "EL-VIS",
+ 82→ description: "Auto-ported from the Winamp AVS preset \"EL-VIS - Molecules (S_KuPeRS Remix).avs\" by EL-VIS. The original is driven by DynamicMovement, Color Map, Invert, which the script engine cannot run -- replaced with a generated \"swirl\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 83→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/EL-VIS - Molecules (S_KuPeRS Remix).avs",
+ 84→ approx: true,
+ 85→ content: "name: \"Molecules (S_KuPeRS Remix)\"\nmode: lines\nblend: additive\npersist: 0.88\nlinewidth: 1.388\n\n[init]\nn = 1119\n\n[frame]\nt = t + dt * (0.211 + bass_att * 1.8)\nhue = hue + dt * 0.042 + beat * 0.012\n\n[point]\narm = floor(i * 3)\nfa = frac(i * 3)\nd = fa\nang = arm * 1.551 + d * 1.047 * 3.14159 + t\nrad = (0.04 + pow(d, 1.714) * 1.15) * (0.7 + bass * 0.5)\nx = sin(ang) * rad / aspect\ny = cos(ang) * rad\nred = (0.3 + 0.7 * d) * (0.3 + mid) * abs(sin(d * 2.325))\ngreen = (0.3 + 0.7 * d) * (0.3 + mid) * 0.22\nblue = (0.3 + 0.7 * d) * (0.3 + mid) * abs(cos(d * 2.325))\nskip = if(below(fa, 0.04), 1, 0)\n",
+ 86→ },
+ 87→ {
+ 88→ folder: "root/embedded/Amp Picks",
+ 89→ name: "Take the Veil",
+ 90→ author: "L1quid",
+ 91→ description: "Auto-ported from the Winamp AVS preset \"L1quid - Take the Veil.avs\" by L1quid. The original is driven by unportable scope code (frame line 15: Unexpected character '$'), which the script engine cannot run -- replaced with a generated \"lissajous\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 92→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/L1quid - Take the Veil.avs",
+ 93→ approx: true,
+ 94→ content: "name: \"Take the Veil\"\nmode: lines\nblend: additive\npersist: 0.895\nlinewidth: 1.78\n\n[init]\nn = 674\n\n[frame]\nt = t + dt * 0.275\nhue = hue + dt * 0.052\n\n[point]\nph = i * 6.28318 * 1.521\nfa = 3 + mid * 0.35\nfb = 6 + bass * 0.45\nx = sin(ph * fa + t) * (0.5 + bass * 0.3) / aspect\ny = sin(ph * fb + t * 1.3 + 0.957) * (0.5 + treb * 0.35)\nred = 0.3 + 0.45 * sin(ph * 2 + t) + abs(v) * 0.6 * (0.25 + bass * 0.95)\ngreen = 0.3 + 0.45 * sin(ph * 2 + t) + abs(v) * 0.6 * (0.25 + mid * 0.95)\nblue = 0.3 + 0.45 * sin(ph * 2 + t) + abs(v) * 0.6 * (0.25 + treb * 0.95)\n",
+ 95→ },
+ 96→ {
+ 97→ folder: "root/embedded/Amp Picks",
+ 98→ name: "Rototronik",
+ 99→ author: "Nic01",
+ 100→ description: "Auto-ported from the Winamp AVS preset \"Nic01 - Rototronik.avs\" by Nic01. Original superscope code, machine-translated to the AVA script dialect.",
+ 101→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/Nic01 - Rototronik.avs",
+ 102→ approx: false,
+ 103→ content: "# Auto-ported from \"Nic01 - Rototronik.avs\" (Winamp AVS / Nic01)\n# SuperScope point program, 4 AVSTrans macros expanded.\nname: \"Rototronik\"\nauthor: \"Nic01\"\nmode: lines\nblend: additive\npersist: 0.88\nlinewidth: 2\nysign: -1\n\n[init]\npi=acos(-1);n=1000;\n\n[frame]\nrx=rx+rdx;ry=ry+rdy;rz=rz+rdz;xs=sin(rx);ys=sin(ry);zs=sin(rz);xc=cos(rx);yc=cos(ry);zc=cos(rz);t=t+0.1;p=0;p1=0.;\n\n[beat]\nrdx=(getosc(0,0,0)*500-250)%250/2500;rdy=(getosc(0.05,0,0)*500-250)%250/2500;rdz=(getosc(0.1,0,0)*500-250)%250/2500;\n\n[point]\np=p+1;\r\np1=if(equal(p%20,1),p1+1,p1);\r\ns=(1-equal(p%20,1));\r\ns1=1-s1;\r\nx1=sin(i*pi*4)*s*s1;\r\ny1=cos(i*pi*4)*s*s1;\r\nx1=x1+getosc((p1%25)*0.04,0,0)*x1;\r\ny1=y1+getosc((p1%25)*0.04,0,0)*y1;\r\nz1=-below(p,500)*0.5+0.25;\r\nc=(2-x1*x1-y1*y1);\r\n;\r\ny2=y1*xc-z1*xs;z2=y1*xs+z1*xc;\r\nx2=z2*ys+x1*yc;z1=z2*yc-x1*ys;\r\nx1=x2*zc-y2*zs;y1=y2*zc+x2*zs;\r\nx=x1/(2+z1);y=y1/(2+z1);\r\nred=c;\r\ngreen=1-c;\r\nc=equal(p,500)*10;\r\nred=red-c;green=green-c;\n",
+ 104→ },
+ 105→ {
+ 106→ folder: "root/embedded/Amp Picks",
+ 107→ name: "...hidden track...",
+ 108→ author: "S_KuPeRS",
+ 109→ description: "Auto-ported from the Winamp AVS preset \"S_KuPeRS - ...hidden track....avs\" by S_KuPeRS. The original is driven by DynamicMovement, Color Map, which the script engine cannot run -- replaced with a generated \"pulse rings\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 110→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/S_KuPeRS - ...hidden track....avs",
+ 111→ approx: true,
+ 112→ content: "name: \"...hidden track...\"\nmode: lines\nblend: additive\npersist: 0.805\nlinewidth: 2.555\n\n[init]\nn = 205\n\n[frame]\nhue = hue + dt * 0.045 + beat * 0.01\ntravel = travel + dt * (0.253 + bass_att * 1.4)\nflash = flash * 0.86\n\n[beat]\nflash = 1\n\n[point]\ng = floor(i * 5)\ngi = frac(i * 5)\nph = frac(rand(g * 5.1 + 1) + travel * (0.55 + rand(g + 17) * 0.85))\na = gi * 6.28318\nrad = ph * (0.232 + bass * 0.659 + flash * 0.08)\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nfade = sin(ph * 3.14159)\nred = min(1, fade * (0.35 + flash * 0.4) * 1.3)\ngreen = fade * (0.35 + flash * 0.4) * 0.5\nblue = fade * (0.35 + flash * 0.4) * 0.12\nskip = if(above(ph, 0.96), 1, 0)\n",
+ 113→ },
+ 114→ {
+ 115→ folder: "root/embedded/Amp Picks",
+ 116→ name: "Arctic Rainbow",
+ 117→ author: "S_KuPeRS",
+ 118→ description: "Auto-ported from the Winamp AVS preset \"S_KuPeRS - Arctic Rainbow.avs\" by S_KuPeRS. The original is driven by BlitterFeedback, DynamicMovement, Movement, which the script engine cannot run -- replaced with a generated \"rain\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 119→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/S_KuPeRS - Arctic Rainbow.avs",
+ 120→ approx: true,
+ 121→ content: "name: \"Arctic Rainbow\"\nmode: dots\nblend: additive\npersist: 0.789\nlinewidth: 1.877\n\n[init]\ncols = 48\nn = 918\n\n[frame]\nfall = fall + dt * (0.855 + vol_att * 1.119)\nhue = hue + dt * 0.034\n\n[point]\ncol = floor(i * cols)\nci = frac(i * cols)\nxx = (col / cols - 0.5) * 1.9\nyf = frac(rand(col * 3.7 + 1) + fall * (0.55 + rand(col + 5) * 0.5))\ntail = rand(col * 1.3 + 9) * 0.297 + 0.04\nyy = (yf * 2 - 1) * 1.05 + ci * tail\nx = xx / aspect\ny = yy\nred = (1 - ci) * (0.2 + treb * 0.85) + 0.04 * 0.85\ngreen = (1 - ci) * (0.2 + treb * 0.85) + 0.04 * 0.92\nblue = (1 - ci) * (0.2 + treb * 0.85) + 0.04\nskip = if(above(abs(yy), 1.02), 1, 0)\n",
+ 122→ },
+ 123→ {
+ 124→ folder: "root/embedded/Amp Picks",
+ 125→ name: "Chinook",
+ 126→ author: "S_KuPeRS",
+ 127→ description: "Auto-ported from the Winamp AVS preset \"S_KuPeRS - Chinook.avs\" by S_KuPeRS. The original is driven by ColorClip, ClearScreen, DynamicMovement, which the script engine cannot run -- replaced with a generated \"spectrum bars\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 128→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/S_KuPeRS - Chinook.avs",
+ 129→ approx: true,
+ 130→ content: "name: \"Chinook\"\nmode: dots\nblend: additive\npersist: 0.831\nlinewidth: 2.299\n\n[init]\nbars = 30\nn = bars * 9\n\n[frame]\nrot = rot + dt * (0.07 + mid * 0.3)\nhue = hue + dt * 0.106\n\n[point]\nbar = floor(i * bars)\nbi = frac(i * bars)\nlvl = getspec(bar / bars, 0, 0)\nh = lvl * 0.893\na = bar * 6.28318 / bars + rot\nrad = 0.142 + bi * h\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = 0.25 + bi * 0.75 * 0.3\ngreen = 0.25 + bi * 0.75 * 0.65\nblue = min(1, 0.25 + bi * 0.75 * 1.3)\nskip = if(above(bi, 0.97), 1, 0)\n",
+ 131→ },
+ 132→ {
+ 133→ folder: "root/embedded/Amp Picks",
+ 134→ name: "Hexual Healing",
+ 135→ author: "S_KuPeRS",
+ 136→ description: "Auto-ported from the Winamp AVS preset \"S_KuPeRS - Hexual Healing.avs\" by S_KuPeRS. The original is driven by Color Map, which the script engine cannot run -- replaced with a generated \"lattice\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 137→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/S_KuPeRS - Hexual Healing.avs",
+ 138→ approx: true,
+ 139→ content: "name: \"Hexual Healing\"\nmode: dots\nblend: additive\npersist: 0.785\nlinewidth: 2.432\n\n[init]\ncols = 34\nn = cols * cols\n\n[frame]\nrot = rot + dt * (0.072 + mid * 0.4)\nhue = hue + dt * 0.062\n\n[point]\ncx = floor(i * cols) / (cols - 1)\ncy = frac(i * cols)\nh = sv * 0.506\nux = (cx - 0.5) * 1.674\nuy = (cy - 0.5) * 1.674\nrx = ux * cos(rot) - uy * sin(rot)\nry = ux * sin(rot) + uy * cos(rot)\nx = (rx / aspect) * (1 - h * 0.7)\ny = ry * (1 - h * 0.7)\nred = min(1, 0.22 + h * 2.2 * 1.3)\ngreen = 0.22 + h * 2.2 * 0.5\nblue = 0.22 + h * 2.2 * 0.12\nsize = 1.2 + h * 5\n",
+ 140→ },
+ 141→ {
+ 142→ folder: "root/embedded/Amp Picks",
+ 143→ name: "Inverse Kinetics",
+ 144→ author: "S_KuPeRS",
+ 145→ description: "Auto-ported from the Winamp AVS preset \"S_KuPeRS - Inverse Kinetics.avs\" by S_KuPeRS. The original is driven by effect#2176, DynamicMovement, which the script engine cannot run -- replaced with a generated \"fountain\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 146→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/S_KuPeRS - Inverse Kinetics.avs",
+ 147→ approx: true,
+ 148→ content: "name: \"Inverse Kinetics\"\nmode: dots\nblend: additive\npersist: 0.805\nlinewidth: 2.613\n\n[init]\nn = 817\n\n[frame]\ntt = tt + dt * (0.5 + bass_att * 1.1)\nhue = hue + dt * 0.103\nburst = burst * 0.9\n\n[beat]\nburst = 1\n\n[point]\njet = floor(i * 2)\nlife = frac(rand(i * 7.7 + jet) + tt * (0.35 + rand(i + 3) * 0.65))\na = jet * 6.28318 / 2 + rand(i * 5.1 + jet) * 0.736\nspd = 0.4 + rand(i * 9.2 + jet) * 0.8\npx = cos(a) * spd * life\npy = sin(a) * spd * life - 1.2 * life * life\nx = px * (0.85 + bass * 0.35) / aspect\ny = py\nred = max(0, 1 - life) * (0.35 + burst * 0.55) * abs(sin(life * 4))\ngreen = max(0, 1 - life) * (0.35 + burst * 0.55) * 0.22\nblue = max(0, 1 - life) * (0.35 + burst * 0.55) * abs(cos(life * 4))\nskip = if(above(life, 0.94), 1, 0)\n",
+ 149→ },
+ 150→ {
+ 151→ folder: "root/embedded/Amp Picks",
+ 152→ name: "Kingish",
+ 153→ author: "S_KuPeRS",
+ 154→ description: "Auto-ported from the Winamp AVS preset \"S_KuPeRS - Kingish.avs\" by S_KuPeRS. The original is driven by BlitterFeedback, which the script engine cannot run -- replaced with a generated \"web\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 155→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/S_KuPeRS - Kingish.avs",
+ 156→ approx: true,
+ 157→ content: "name: \"Kingish\"\nmode: dots\nblend: additive\npersist: 0.813\nlinewidth: 2.025\n\n[init]\nspokes = 27\nn = spokes * 22\n\n[frame]\nrot = rot + dt * (0.102 + mid * 0.4)\nt = t + dt * 2.987\nhue = hue + dt * 0.075\n\n[point]\nsp = floor(i * spokes)\nsi = frac(i * spokes)\na = sp * 6.28318 / spokes + rot\nwave = sin(si * 2 * 3.14159 - t) * (0.03 + bass * 0.28)\nrad = 0.12 + si * 0.92 + wave\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = (1 - si) * 0.45 + 0.15 + sv * 0.55 * abs(sin(sp * 0.4))\ngreen = (1 - si) * 0.45 + 0.15 + sv * 0.55 * 0.22\nblue = (1 - si) * 0.45 + 0.15 + sv * 0.55 * abs(cos(sp * 0.4))\nskip = if(above(si, 0.985), 1, 0)\n",
+ 158→ },
+ 159→ {
+ 160→ folder: "root/embedded/Amp Picks",
+ 161→ name: "Spirit Realm (Degnic's Plasmoid RMX)",
+ 162→ author: "S_KuPeRS",
+ 163→ description: "Auto-ported from the Winamp AVS preset \"S_KuPeRS - Spirit Realm (Degnic's Plasmoid RMX).avs\" by S_KuPeRS. The original is driven by BlitterFeedback, \u0005, DotPlane, which the script engine cannot run -- replaced with a generated \"ribbon\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 164→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/S_KuPeRS - Spirit Realm (Degnic's Plasmoid RMX).avs",
+ 165→ approx: true,
+ 166→ content: "name: \"Spirit Realm (Degnic's Plasmoid RMX)\"\nmode: lines\nblend: additive\npersist: 0.885\nlinewidth: 2.389\n\n[init]\nn = 1199\n\n[frame]\nt = t + dt * (0.344 + bass_att * 0.8)\nsway = sway + dt * (0.5 + treb)\nhue = hue + dt * 0.085\n\n[point]\na = i * 6.28318 * 2\nwob = sin(a * 3 + t * 2) * (0.08 + treb * 0.3)\nrad = (0.42 + mid * 0.25) * (0.85 + 0.15 * sin(a * 3 + sway)) + v * 0.422 - wob\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = 0.3 + abs(v) * 1.2 * (0.45 + 0.55 * sin(hue * 6.28318 + a * 0.5))\ngreen = 0.3 + abs(v) * 1.2 * (0.45 + 0.55 * sin(hue * 6.28318 + a * 0.5 + 2.09))\nblue = 0.3 + abs(v) * 1.2 * (0.45 + 0.55 * sin(hue * 6.28318 + a * 0.5 + 4.19))\n",
+ 167→ },
+ 168→ {
+ 169→ folder: "root/embedded/Amp Picks",
+ 170→ name: "Texering 002",
+ 171→ author: "S_KuPeRS",
+ 172→ description: "Auto-ported from the Winamp AVS preset \"S_KuPeRS - Texering 002.avs\" by S_KuPeRS. Original superscope code, machine-translated to the AVA script dialect.",
+ 173→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/S_KuPeRS - Texering 002.avs",
+ 174→ approx: false,
+ 175→ content: "# Auto-ported from \"S_KuPeRS - Texering 002.avs\" (Winamp AVS / S_KuPeRS)\n# SuperScope point program, 4 AVSTrans macros expanded.\nname: \"Texering 002\"\nauthor: \"S_KuPeRS\"\nmode: dots\nblend: additive\npersist: 0.78\nlinewidth: 3\nysign: -1\n\n[init]\nn=100;pi=acos(-1);rx=1.57;ry=1.57;rx=0;ry=0;rz=0;rxo=0;ryo=0;rzo=0;rxt=0;ryt=0;rzt=0;\n\n[frame]\nredf=redf+redr;greenf=greenf+greenr;bluef=bluef+bluer;rx=rx+rxo;ry=ry+ryo;rz=rz+rzo;rzo=rzo*0.97;cx=cos(sin(rx));sx=sin(sin(rx));cy=cos(sin(ry));sy=sin(sin(ry));cz=cos(rz);sz=sin(rz);\n\n[beat]\nredr=rnd(10)/100-0.05;greenr=rnd(10)/100-0.05;bluer=rnd(10)/100-0.05;rxo=getosc(0.5,0.02,0)/12;ryo=getosc(0.3,0.02,0)/12;rzo=getosc(0.7,0.02,0)/8;\n\n[point]\nr=i*pi*2;r2=i*pi*99;\r\nx1=sin(r-bluef*1.53+.0776)*cos(r2+greenf);\r\ny1=cos(r+bluef);\r\nz1=sin(r2+redf);\r\n\r\nx1d=x1*cz-y1*sz;\r\ny1d=x1*sz+y1*cz;\r\ny2d=y1d*cx-z1*sx;\r\nz2d=y1d*sx+z1*cx;\r\nx3d=x1d*cy-z2d*sy;\r\nz3d=x1d*sy+z2d*cy;\r\nzi=1/(z3d+3);\r\nx=if(above(z3d+3,0.1),x3d*zi,x);\r\ny=if(above(z3d+3,0.1),y2d*zi,y);\r\n\r\nalpha=if(below(z3d+2.4,0),0,1);\r\nred=alpha*(1+sin(redf)/4-z3d/4)/7;\r\nblue=alpha*(1+sin(bluef)/4-z3d)/20;\r\ngreen=alpha*(1+sin(greenf)/3-z3d/2)/12;\n",
+ 176→ },
+ 177→ {
+ 178→ folder: "root/embedded/Amp Picks",
+ 179→ name: "Upward Current",
+ 180→ author: "S_KuPeRS",
+ 181→ description: "Auto-ported from the Winamp AVS preset \"S_KuPeRS - Upward Current.avs\" by S_KuPeRS. The original is driven by CustomBPM, DynamicMovement, Water, which the script engine cannot run -- replaced with a generated \"rose\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 182→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/S_KuPeRS - Upward Current.avs",
+ 183→ approx: true,
+ 184→ content: "name: \"Upward Current\"\nmode: lines\nblend: additive\npersist: 0.914\nlinewidth: 1.629\n\n[init]\nn = 869\n\n[frame]\nt = t + dt * (0.134 + mid * 0.5)\nhue = hue + dt * 0.122\nflash = flash * 0.87\n\n[beat]\nflash = 1\n\n[point]\nth = i * 6.28318 * 2 + t\nr = abs(cos(2 * th + bass * 0.656))\nrad = r * (0.5 + bass * 0.3 + sv * 0.2 + flash * 0.08)\nx = sin(th) * rad / aspect\ny = cos(th) * rad\nred = 0.25 + 0.65 * r + v * 0.25 * abs(sin(th * 0.5))\ngreen = 0.25 + 0.65 * r + v * 0.25 * 0.22\nblue = 0.25 + 0.65 * r + v * 0.25 * abs(cos(th * 0.5))\n",
+ 185→ },
+ 186→ {
+ 187→ folder: "root/embedded/Amp Picks",
+ 188→ name: "Aqua-fumes",
+ 189→ author: "Tuggummi",
+ 190→ description: "Auto-ported from the Winamp AVS preset \"Tuggummi - Aqua-fumes.avs\" by Tuggummi. The original is driven by Holden03: Convolution Filter, Color Map, Bump, which the script engine cannot run -- replaced with a generated \"starfield\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 191→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/Tuggummi - Aqua-fumes.avs",
+ 192→ approx: true,
+ 193→ content: "name: \"Aqua-fumes\"\nmode: dots\nblend: additive\npersist: 0.787\nlinewidth: 2.043\n\n[init]\nn = 966\n\n[frame]\ntravel = travel + dt * (0.347 + vol_att * 1.816)\nhue = hue + dt * 0.076\n\n[point]\na = rand(i * 7.3) * 6.28318\nz = frac(rand(i * 3.1 + 5) + travel * (0.5 + rand(i + 9) * 0.7))\nrad = z * z * 1.075\nx = sin(a) * rad / aspect\ny = cos(a) * rad\ntw = 0.4 + 0.6 * sin(z * 40 + time * 3) * (0.5 + treb)\nred = tw * abs(sin(i * 3.959))\ngreen = tw * 0.22\nblue = tw * abs(cos(i * 3.959))\nsize = 1 + z * 3 * (0.5 + bass)\nskip = if(above(z, 0.97), 1, 0)\n",
+ 194→ },
+ 195→ {
+ 196→ folder: "root/embedded/Amp Picks",
+ 197→ name: "Club Seizure (color enhanced mix)",
+ 198→ author: "Tuggummi",
+ 199→ description: "Auto-ported from the Winamp AVS preset \"Tuggummi - Club Seizure (color enhanced mix).avs\" by Tuggummi. The original is driven by Movement, DynamicMovement, Multiplier, which the script engine cannot run -- replaced with a generated \"helix\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 200→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/Tuggummi - Club Seizure (color enhanced mix).avs",
+ 201→ approx: true,
+ 202→ content: "name: \"Club Seizure (color enhanced mix)\"\nmode: lines\nblend: additive\npersist: 0.864\nlinewidth: 2.379\n\n[init]\nn = 946\n\n[frame]\nt = t + dt * (0.322 + bass_att * 0.9)\nhue = hue + dt * 0.089\nflash = flash * 0.86\n\n[beat]\nflash = 1\n\n[point]\nst = floor(i * 2)\nsi = frac(i * 2)\ns = si * 6 * 6.28318 + t\nph = st * 3.14159\nrad = (0.28 + bass * 0.32 + flash * 0.06) * (0.55 + 0.45 * (cos(s + ph) * 0.5 + 0.5))\nx = sin(s + ph) * rad / aspect\ny = (si - 0.5) * 1.8\nred = 0.25 + 0.55 * (cos(s + ph) * 0.5 + 0.5) + treb * 0.25 * (0.45 + 0.55 * sin(hue * 6.28318 + s * 0.3))\ngreen = 0.25 + 0.55 * (cos(s + ph) * 0.5 + 0.5) + treb * 0.25 * (0.45 + 0.55 * sin(hue * 6.28318 + s * 0.3 + 2.09))\nblue = 0.25 + 0.55 * (cos(s + ph) * 0.5 + 0.5) + treb * 0.25 * (0.45 + 0.55 * sin(hue * 6.28318 + s * 0.3 + 4.19))\n",
+ 203→ },
+ 204→ {
+ 205→ folder: "root/embedded/Amp Picks",
+ 206→ name: "Electro blobs",
+ 207→ author: "Tuggummi",
+ 208→ description: "Auto-ported from the Winamp AVS preset \"Tuggummi - Electro blobs.avs\" by Tuggummi. The original is driven by MovingParticle, Color Map, BlitterFeedback, which the script engine cannot run -- replaced with a generated \"kaleido\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 209→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/Tuggummi - Electro blobs.avs",
+ 210→ approx: true,
+ 211→ content: "name: \"Electro blobs\"\nmode: lines\nblend: additive\npersist: 0.866\nlinewidth: 1.536\n\n[init]\nsegs = 12\nn = 616\n\n[frame]\nrot = rot + dt * (0.085 + mid_att * 0.5)\nhue = hue + dt * 0.131\nflash = flash * 0.85\n\n[beat]\nflash = 1\n\n[point]\nseg = floor(i * segs)\nsi = frac(i * segs)\na0 = si * 6.28318 / segs + rot\namp = 0.1 + sv * 0.55 + bass * 0.25 + flash * 0.06\nrad = 0.15 + amp * (0.4 + 0.6 * sin(a0 * 5 + time * 2))\na = seg * 6.28318 / segs + a0 * if(below(seg % 2, 1), 1, -1)\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = 0.3 + amp * 1.3 * 0.85\ngreen = 0.3 + amp * 1.3 * 0.92\nblue = 0.3 + amp * 1.3\nskip = if(above(si, 0.96), 1, 0)\n",
+ 212→ },
+ 213→ {
+ 214→ folder: "root/embedded/Amp Picks",
+ 215→ name: "Kaleidofied",
+ 216→ author: "Tuggummi",
+ 217→ description: "Auto-ported from the Winamp AVS preset \"Tuggummi - Kaleidofied.avs\" by Tuggummi. The original is driven by Invert, Movement, Bump, which the script engine cannot run -- replaced with a generated \"ribbon\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 218→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/Tuggummi - Kaleidofied.avs",
+ 219→ approx: true,
+ 220→ content: "name: \"Kaleidofied\"\nmode: lines\nblend: additive\npersist: 0.885\nlinewidth: 1.542\n\n[init]\nn = 1126\n\n[frame]\nt = t + dt * (0.589 + bass_att * 0.8)\nsway = sway + dt * (0.5 + treb)\nhue = hue + dt * 0.1\n\n[point]\na = i * 6.28318 * 1\nwob = sin(a * 3 + t * 2) * (0.08 + treb * 0.3)\nrad = (0.42 + mid * 0.25) * (0.85 + 0.15 * sin(a * 3 + sway)) + v * 0.232 - wob\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = 0.3 + abs(v) * 1.2 * (0.45 + 0.55 * sin(hue * 6.28318 + a * 0.5))\ngreen = 0.3 + abs(v) * 1.2 * (0.45 + 0.55 * sin(hue * 6.28318 + a * 0.5 + 2.09))\nblue = 0.3 + abs(v) * 1.2 * (0.45 + 0.55 * sin(hue * 6.28318 + a * 0.5 + 4.19))\n",
+ 221→ },
+ 222→ {
+ 223→ folder: "root/embedded/Amp Picks",
+ 224→ name: "Magma Flow II",
+ 225→ author: "Tuggummi",
+ 226→ description: "Auto-ported from the Winamp AVS preset \"Tuggummi - Magma Flow II.avs\" by Tuggummi. The original is driven by Color Map, BufferSave, ClearScreen, which the script engine cannot run -- replaced with a generated \"orbits\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 227→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/Tuggummi - Magma Flow II.avs",
+ 228→ approx: true,
+ 229→ content: "name: \"Magma Flow II\"\nmode: dots\nblend: additive\npersist: 0.804\nlinewidth: 2.633\n\n[init]\nrings = 3\nn = rings * 104\n\n[frame]\nt = t + dt * (0.258 + bass_att * 0.6)\nhue = hue + dt * 0.036\nsquash = 0.387 + 0.25 * sin(t * 0.203)\n\n[point]\nring = floor(i * rings)\nri3 = frac(i * rings)\nr0 = 0.16 + ring * 0.148\na = ri3 * 6.28318 + t * (1.2 - ring * 0.18) + ring * 2.4\nx = sin(a) * r0 * (1 + bass * 0.18) / aspect\ny = cos(a) * r0 * squash\nsat = frac(ri3 * 5)\nsize = if(below(sat, 0.14), 3 + bass * 3, 1)\nred = 0.25 + bass * 0.5 + ring * 0.07 * abs(sin(ring * 1.3))\ngreen = 0.25 + bass * 0.5 + ring * 0.07 * 0.22\nblue = 0.25 + bass * 0.5 + ring * 0.07 * abs(cos(ring * 1.3))\n",
+ 230→ },
+ 231→ {
+ 232→ folder: "root/embedded/Amp Picks",
+ 233→ name: "Multiple Orgasms",
+ 234→ author: "Tuggummi",
+ 235→ description: "Auto-ported from the Winamp AVS preset \"Tuggummi - Multiple Orgasms.avs\" by Tuggummi. The original is driven by Movement, Color Map, BufferSave, which the script engine cannot run -- replaced with a generated \"web\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 236→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/Tuggummi - Multiple Orgasms.avs",
+ 237→ approx: true,
+ 238→ content: "name: \"Multiple Orgasms\"\nmode: lines\nblend: additive\npersist: 0.873\nlinewidth: 1.6\n\n[init]\nspokes = 30\nn = spokes * 33\n\n[frame]\nrot = rot + dt * (0.106 + mid * 0.4)\nt = t + dt * 2.164\nhue = hue + dt * 0.106\n\n[point]\nsp = floor(i * spokes)\nsi = frac(i * spokes)\na = sp * 6.28318 / spokes + rot\nwave = sin(si * 4 * 3.14159 - t) * (0.03 + bass * 0.28)\nrad = 0.12 + si * 0.919 + wave\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = (1 - si) * 0.45 + 0.15 + sv * 0.55 * (0.45 + 0.55 * sin(hue * 6.28318 + sp * 0.4))\ngreen = (1 - si) * 0.45 + 0.15 + sv * 0.55 * (0.45 + 0.55 * sin(hue * 6.28318 + sp * 0.4 + 2.09))\nblue = (1 - si) * 0.45 + 0.15 + sv * 0.55 * (0.45 + 0.55 * sin(hue * 6.28318 + sp * 0.4 + 4.19))\nskip = if(above(si, 0.985), 1, 0)\n",
+ 239→ },
+ 240→ {
+ 241→ folder: "root/embedded/Amp Picks",
+ 242→ name: "Pina Colada",
+ 243→ author: "Tuggummi",
+ 244→ description: "Auto-ported from the Winamp AVS preset \"Tuggummi - Pina Colada.avs\" by Tuggummi. The original is driven by Color Map, which the script engine cannot run -- replaced with a generated \"swirl\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 245→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/Tuggummi - Pina Colada.avs",
+ 246→ approx: true,
+ 247→ content: "name: \"Pina Colada\"\nmode: lines\nblend: additive\npersist: 0.887\nlinewidth: 1.779\n\n[init]\nn = 943\n\n[frame]\nt = t + dt * (0.219 + bass_att * 1.8)\nhue = hue + dt * 0.098 + beat * 0.012\n\n[point]\narm = floor(i * 8)\nfa = frac(i * 8)\nd = fa\nang = arm * 1.519 + d * 1.263 * 3.14159 + t\nrad = (0.04 + pow(d, 2.354) * 1.15) * (0.7 + bass * 0.5)\nx = sin(ang) * rad / aspect\ny = cos(ang) * rad\nred = (0.3 + 0.7 * d) * (0.3 + mid) * (0.25 + bass * 0.95)\ngreen = (0.3 + 0.7 * d) * (0.3 + mid) * (0.25 + mid * 0.95)\nblue = (0.3 + 0.7 * d) * (0.3 + mid) * (0.25 + treb * 0.95)\nskip = if(below(fa, 0.04), 1, 0)\n",
+ 248→ },
+ 249→ {
+ 250→ folder: "root/embedded/Amp Picks",
+ 251→ name: "Scope in Spotlight II",
+ 252→ author: "Tuggummi",
+ 253→ description: "Auto-ported from the Winamp AVS preset \"Tuggummi - Scope in Spotlight II.avs\" by Tuggummi. The original is driven by effect#117, Color Map, Invert, which the script engine cannot run -- replaced with a generated \"kaleido\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 254→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/Tuggummi - Scope in Spotlight II.avs",
+ 255→ approx: true,
+ 256→ content: "name: \"Scope in Spotlight II\"\nmode: lines\nblend: additive\npersist: 0.854\nlinewidth: 2.056\n\n[init]\nsegs = 7\nn = 462\n\n[frame]\nrot = rot + dt * (0.107 + mid_att * 0.5)\nhue = hue + dt * 0.035\nflash = flash * 0.85\n\n[beat]\nflash = 1\n\n[point]\nseg = floor(i * segs)\nsi = frac(i * segs)\na0 = si * 6.28318 / segs + rot\namp = 0.1 + sv * 0.55 + bass * 0.25 + flash * 0.06\nrad = 0.15 + amp * (0.4 + 0.6 * sin(a0 * 4 + time * 2))\na = seg * 6.28318 / segs + a0 * if(below(seg % 2, 1), 1, -1)\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = min(1, 0.3 + amp * 1.3 * 1.3)\ngreen = 0.3 + amp * 1.3 * 0.5\nblue = 0.3 + amp * 1.3 * 0.12\nskip = if(above(si, 0.96), 1, 0)\n",
+ 257→ },
+ 258→ {
+ 259→ folder: "root/embedded/Amp Picks",
+ 260→ name: "Solero Shots",
+ 261→ author: "Tuggummi",
+ 262→ description: "Auto-ported from the Winamp AVS preset \"Tuggummi - Solero Shots.avs\" by Tuggummi. The original is driven by effect#28, FastBright, Movement, which the script engine cannot run -- replaced with a generated \"spectrum bars\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 263→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/Tuggummi - Solero Shots.avs",
+ 264→ approx: true,
+ 265→ content: "name: \"Solero Shots\"\nmode: lines\nblend: additive\npersist: 0.878\nlinewidth: 2.226\n\n[init]\nbars = 40\nn = bars * 12\n\n[frame]\nrot = rot + dt * (0.078 + mid * 0.3)\nhue = hue + dt * 0.032\n\n[point]\nbar = floor(i * bars)\nbi = frac(i * bars)\nlvl = getspec(bar / bars, 0, 0)\nh = lvl * 0.813\na = bar * 6.28318 / bars + rot\nrad = 0.218 + bi * h\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = 0.25 + bi * 0.75 * 0.3\ngreen = 0.25 + bi * 0.75 * 0.65\nblue = min(1, 0.25 + bi * 0.75 * 1.3)\nskip = if(above(bi, 0.97), 1, 0)\n",
+ 266→ },
+ 267→ {
+ 268→ folder: "root/embedded/Amp Picks",
+ 269→ name: "Square Minded",
+ 270→ author: "Tuggummi",
+ 271→ description: "Auto-ported from the Winamp AVS preset \"Tuggummi - Square Minded.avs\" by Tuggummi. The original is driven by FastBright, which the script engine cannot run -- replaced with a generated \"moire\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 272→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/Tuggummi - Square Minded.avs",
+ 273→ approx: true,
+ 274→ content: "name: \"Square Minded\"\nmode: dots\nblend: additive\npersist: 0.783\nlinewidth: 2.696\n\n[init]\nn = 1492\n\n[frame]\nt = t + dt * 0.656\nhue = hue + dt * 0.13\n\n[point]\nu = rand(i * 1.7) * 2 - 1\nw = rand(i * 3.3 + 11) * 2 - 1\nm = sin((u * 3.356 + w * 3.959 + t) * 3.14159) * sin((u * 3.01 - w * 6.626 - t * 0.7) * 3.14159)\nx = u * 0.994 / aspect\ny = w * 0.856\nlum = max(0, m) * (0.3 + bass * 0.9) + 0.06\nred = lum * 0.85\ngreen = lum * 0.92\nblue = lum\nsize = 1 + abs(m) * 3.211\n",
+ 275→ },
+ 276→ {
+ 277→ folder: "root/embedded/Amp Picks",
+ 278→ name: "Worm-O-Rama (Raz Remix)",
+ 279→ author: "Tuggummi",
+ 280→ description: "Auto-ported from the Winamp AVS preset \"Tuggummi - Worm-O-Rama (Raz Remix).avs\" by Tuggummi. The original is driven by MovingParticle, Filter, Simple, which the script engine cannot run -- replaced with a generated \"ribbon\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 281→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/Tuggummi - Worm-O-Rama (Raz Remix).avs",
+ 282→ approx: true,
+ 283→ content: "name: \"Worm-O-Rama (Raz Remix)\"\nmode: lines\nblend: additive\npersist: 0.894\nlinewidth: 1.933\n\n[init]\nn = 1189\n\n[frame]\nt = t + dt * (0.418 + bass_att * 0.8)\nsway = sway + dt * (0.5 + treb)\nhue = hue + dt * 0.102\n\n[point]\na = i * 6.28318 * 3\nwob = sin(a * 3 + t * 2) * (0.08 + treb * 0.3)\nrad = (0.42 + mid * 0.25) * (0.85 + 0.15 * sin(a * 3 + sway)) + v * 0.282 - wob\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = 0.3 + abs(v) * 1.2 * (0.25 + bass * 0.95)\ngreen = 0.3 + abs(v) * 1.2 * (0.25 + mid * 0.95)\nblue = 0.3 + abs(v) * 1.2 * (0.25 + treb * 0.95)\n",
+ 284→ },
+ 285→ {
+ 286→ folder: "root/embedded/Amp Picks",
+ 287→ name: "Zero-G Maze",
+ 288→ author: "UnConeD",
+ 289→ description: "Auto-ported from the Winamp AVS preset \"UnConeD - Zero-G Maze.avs\" by UnConeD. Original superscope code, machine-translated to the AVA script dialect.",
+ 290→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/UnConeD - Zero-G Maze.avs",
+ 291→ approx: false,
+ 292→ content: "# Auto-ported from \"UnConeD - Zero-G Maze.avs\" (Winamp AVS / UnConeD)\n# SuperScope point program, 4 AVSTrans macros expanded.\nname: \"Zero-G Maze\"\nauthor: \"UnConeD\"\nmode: lines\nblend: additive\npersist: 0.88\nlinewidth: 2\nysign: -1\n\n[init]\nn=50;ro=0;go=2.0;bo=4.0\n\n[frame]\nt=t-0.05;aa=aa*0.7;\n\n[beat]\naa=0.0;ro=rnd(100)/100*6.28;go=rnd(100)/100*6.28;bo=rnd(100)/100*6.28\n\n[point]\ny=0;x=i*.5-.1;\r\nred=sin(t+ro+i*2)*.5+.5;\r\ngreen=sin(t+go+i*2)*.5+.5;\r\nblue=sin(t+bo+i*2)*.5+.5;\n",
+ 293→ },
+ 294→ {
+ 295→ folder: "root/embedded/Amp Picks",
+ 296→ name: "Cartoon Trip",
+ 297→ author: "UnConeD",
+ 298→ description: "Auto-ported from the Winamp AVS preset \"UnConeD - Cartoon Trip.avs\" by UnConeD. The original is driven by DynamicMovement, FastBright, Color Reduction, which the script engine cannot run -- replaced with a generated \"spectrum bars\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 299→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/UnConeD - Cartoon Trip.avs",
+ 300→ approx: true,
+ 301→ content: "name: \"Cartoon Trip\"\nmode: lines\nblend: additive\npersist: 0.831\nlinewidth: 2.55\n\n[init]\nbars = 39\nn = bars * 9\n\n[frame]\nrot = rot + dt * (0.116 + mid * 0.3)\nhue = hue + dt * 0.112\n\n[point]\nbar = floor(i * bars)\nbi = frac(i * bars)\nlvl = getspec(bar / bars, 0, 0)\nh = lvl * 0.83\na = bar * 6.28318 / bars + rot\nrad = 0.189 + bi * h\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = 0.25 + bi * 0.75 * 0.85\ngreen = 0.25 + bi * 0.75 * 0.92\nblue = 0.25 + bi * 0.75\nskip = if(above(bi, 0.97), 1, 0)\n",
+ 302→ },
+ 303→ {
+ 304→ folder: "root/embedded/Amp Picks",
+ 305→ name: "Containment",
+ 306→ author: "UnConeD",
+ 307→ description: "Auto-ported from the Winamp AVS preset \"UnConeD - Containment.avs\" by UnConeD. The original is driven by unportable scope code (init line 12: Unexpected character '£'), which the script engine cannot run -- replaced with a generated \"ribbon\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 308→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/UnConeD - Containment.avs",
+ 309→ approx: true,
+ 310→ content: "name: \"Containment\"\nmode: lines\nblend: additive\npersist: 0.935\nlinewidth: 1.8\n\n[init]\nn = 847\n\n[frame]\nt = t + dt * (0.436 + bass_att * 0.8)\nsway = sway + dt * (0.5 + treb)\nhue = hue + dt * 0.071\n\n[point]\na = i * 6.28318 * 2\nwob = sin(a * 2 + t * 2) * (0.08 + treb * 0.3)\nrad = (0.42 + mid * 0.25) * (0.85 + 0.15 * sin(a * 3 + sway)) + v * 0.25 - wob\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = min(1, 0.3 + abs(v) * 1.2 * 1.3)\ngreen = 0.3 + abs(v) * 1.2 * 0.5\nblue = 0.3 + abs(v) * 1.2 * 0.12\n",
+ 311→ },
+ 312→ {
+ 313→ folder: "root/embedded/Amp Picks",
+ 314→ name: "Cubic Flower",
+ 315→ author: "UnConeD",
+ 316→ description: "Auto-ported from the Winamp AVS preset \"UnConeD - Cubic Flower.avs\" by UnConeD. Original superscope code, machine-translated to the AVA script dialect.",
+ 317→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/UnConeD - Cubic Flower.avs",
+ 318→ approx: false,
+ 319→ content: "# Auto-ported from \"UnConeD - Cubic Flower.avs\" (Winamp AVS / UnConeD)\n# SuperScope point program, 4 AVSTrans macros expanded.\nname: \"Cubic Flower\"\nauthor: \"UnConeD\"\nmode: lines\nblend: additive\npersist: 0.88\nlinewidth: 2\nysign: -1\n\n[init]\nn=40;ro=0;go=2.0;bo=4.0\n\n[frame]\nt=t-0.05;aa=aa*0.7;\n\n[beat]\naa=1;ro=rnd(100)/100*6.28;go=rnd(100)/100*6.28;bo=rnd(100)/100*6.28\n\n[point]\nx=i*.8-.4;y=0;\r\nred=sin(t+ro+i*2)/2+.5+aa;\r\ngreen=sin(t+go+i*2)/2+.5+aa;\r\nblue=sin(t+bo+i*2)/2+.5+aa;\r\nblue=blue+1-min(red+green+blue,1)\n",
+ 320→ },
+ 321→ {
+ 322→ folder: "root/embedded/Amp Picks",
+ 323→ name: "Jello Cube",
+ 324→ author: "UnConeD",
+ 325→ description: "Auto-ported from the Winamp AVS preset \"UnConeD - Jello Cube.avs\" by UnConeD. The original is driven by Color Map, which the script engine cannot run -- replaced with a generated \"pulse rings\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 326→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/UnConeD - Jello Cube.avs",
+ 327→ approx: true,
+ 328→ content: "name: \"Jello Cube\"\nmode: lines\nblend: additive\npersist: 0.813\nlinewidth: 2.201\n\n[init]\nn = 620\n\n[frame]\nhue = hue + dt * 0.073 + beat * 0.01\ntravel = travel + dt * (0.196 + bass_att * 1.4)\nflash = flash * 0.86\n\n[beat]\nflash = 1\n\n[point]\ng = floor(i * 10)\ngi = frac(i * 10)\nph = frac(rand(g * 5.1 + 1) + travel * (0.55 + rand(g + 17) * 0.85))\na = gi * 6.28318\nrad = ph * (0.226 + bass * 0.743 + flash * 0.08)\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nfade = sin(ph * 3.14159)\nred = fade * (0.35 + flash * 0.4) * abs(sin(g * 0.7))\ngreen = fade * (0.35 + flash * 0.4) * 0.22\nblue = fade * (0.35 + flash * 0.4) * abs(cos(g * 0.7))\nskip = if(above(ph, 0.96), 1, 0)\n",
+ 329→ },
+ 330→ {
+ 331→ folder: "root/embedded/Amp Picks",
+ 332→ name: "Mercury",
+ 333→ author: "UnConeD",
+ 334→ description: "Auto-ported from the Winamp AVS preset \"UnConeD - Mercury.avs\" by UnConeD. The original is driven by Blur, DynamicMovement, FastBright, which the script engine cannot run -- replaced with a generated \"ribbon\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 335→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/UnConeD - Mercury.avs",
+ 336→ approx: true,
+ 337→ content: "name: \"Mercury\"\nmode: lines\nblend: additive\npersist: 0.888\nlinewidth: 1.97\n\n[init]\nn = 1109\n\n[frame]\nt = t + dt * (0.329 + bass_att * 0.8)\nsway = sway + dt * (0.5 + treb)\nhue = hue + dt * 0.097\n\n[point]\na = i * 6.28318 * 3\nwob = sin(a * 6 + t * 2) * (0.08 + treb * 0.3)\nrad = (0.42 + mid * 0.25) * (0.85 + 0.15 * sin(a * 3 + sway)) + v * 0.31 - wob\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = 0.3 + abs(v) * 1.2 * 0.3\ngreen = 0.3 + abs(v) * 1.2 * 0.65\nblue = min(1, 0.3 + abs(v) * 1.2 * 1.3)\n",
+ 338→ },
+ 339→ {
+ 340→ folder: "root/embedded/Amp Picks",
+ 341→ name: "Neon Coaster",
+ 342→ author: "UnConeD",
+ 343→ description: "Auto-ported from the Winamp AVS preset \"UnConeD - Neon Coaster.avs\" by UnConeD. The original is driven by unportable scope code (beat line 18: Unexpected character '¤'), which the script engine cannot run -- replaced with a generated \"spectrum bars\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 344→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/UnConeD - Neon Coaster.avs",
+ 345→ approx: true,
+ 346→ content: "name: \"Neon Coaster\"\nmode: dots\nblend: additive\npersist: 0.897\nlinewidth: 2.488\n\n[init]\nbars = 33\nn = bars * 12\n\n[frame]\nrot = rot + dt * (0.092 + mid * 0.3)\nhue = hue + dt * 0.066\n\n[point]\nbar = floor(i * bars)\nbi = frac(i * bars)\nlvl = getspec(bar / bars, 0, 0)\nh = lvl * 0.765\na = bar * 6.28318 / bars + rot\nrad = 0.193 + bi * h\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = min(1, 0.25 + bi * 0.75 * 1.3)\ngreen = 0.25 + bi * 0.75 * 0.5\nblue = 0.25 + bi * 0.75 * 0.12\nskip = if(above(bi, 0.97), 1, 0)\n",
+ 347→ },
+ 348→ {
+ 349→ folder: "root/embedded/Amp Picks",
+ 350→ name: "Plankton",
+ 351→ author: "UnConeD",
+ 352→ description: "Auto-ported from the Winamp AVS preset \"UnConeD - Plankton.avs\" by UnConeD. The original is driven by DynamicMovement, FastBright, which the script engine cannot run -- replaced with a generated \"pulse rings\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 353→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/UnConeD - Plankton.avs",
+ 354→ approx: true,
+ 355→ content: "name: \"Plankton\"\nmode: dots\nblend: additive\npersist: 0.83\nlinewidth: 2.37\n\n[init]\nn = 627\n\n[frame]\nhue = hue + dt * 0.033 + beat * 0.01\ntravel = travel + dt * (0.435 + bass_att * 1.4)\nflash = flash * 0.86\n\n[beat]\nflash = 1\n\n[point]\ng = floor(i * 11)\ngi = frac(i * 11)\nph = frac(rand(g * 5.1 + 1) + travel * (0.55 + rand(g + 17) * 0.85))\na = gi * 6.28318\nrad = ph * (0.194 + bass * 0.352 + flash * 0.08)\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nfade = sin(ph * 3.14159)\nred = min(1, fade * (0.35 + flash * 0.4) * 1.3)\ngreen = fade * (0.35 + flash * 0.4) * 0.5\nblue = fade * (0.35 + flash * 0.4) * 0.12\nskip = if(above(ph, 0.96), 1, 0)\n",
+ 356→ },
+ 357→ {
+ 358→ folder: "root/embedded/Amp Picks",
+ 359→ name: "Reflectocube",
+ 360→ author: "UnConeD",
+ 361→ description: "Auto-ported from the Winamp AVS preset \"UnConeD - Reflectocube.avs\" by UnConeD. The original is driven by Movement, DynamicMovement, which the script engine cannot run -- replaced with a generated \"swirl\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 362→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/UnConeD - Reflectocube.avs",
+ 363→ approx: true,
+ 364→ content: "name: \"Reflectocube\"\nmode: lines\nblend: additive\npersist: 0.91\nlinewidth: 1.419\n\n[init]\nn = 1235\n\n[frame]\nt = t + dt * (0.31 + bass_att * 1.8)\nhue = hue + dt * 0.065 + beat * 0.012\n\n[point]\narm = floor(i * 4)\nfa = frac(i * 4)\nd = fa\nang = arm * 1.405 + d * 0.906 * 3.14159 + t\nrad = (0.04 + pow(d, 1.825) * 1.15) * (0.7 + bass * 0.5)\nx = sin(ang) * rad / aspect\ny = cos(ang) * rad\nred = (0.3 + 0.7 * d) * (0.3 + mid) * (0.45 + 0.55 * sin(hue * 6.28318 + d * 4.537))\ngreen = (0.3 + 0.7 * d) * (0.3 + mid) * (0.45 + 0.55 * sin(hue * 6.28318 + d * 4.537 + 2.09))\nblue = (0.3 + 0.7 * d) * (0.3 + mid) * (0.45 + 0.55 * sin(hue * 6.28318 + d * 4.537 + 4.19))\nskip = if(below(fa, 0.04), 1, 0)\n",
+ 365→ },
+ 366→ {
+ 367→ folder: "root/embedded/Amp Picks",
+ 368→ name: "Speeder 3K",
+ 369→ author: "UnConeD",
+ 370→ description: "Auto-ported from the Winamp AVS preset \"UnConeD - Speeder 3K.avs\" by UnConeD. The original is driven by unportable scope code (init line 12: Unexpected character '¤'), which the script engine cannot run -- replaced with a generated \"starfield\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 371→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/UnConeD - Speeder 3K.avs",
+ 372→ approx: true,
+ 373→ content: "name: \"Speeder 3K\"\nmode: dots\nblend: additive\npersist: 0.831\nlinewidth: 2.272\n\n[init]\nn = 949\n\n[frame]\ntravel = travel + dt * (0.156 + vol_att * 1.231)\nhue = hue + dt * 0.04\n\n[point]\na = rand(i * 7.3) * 6.28318\nz = frac(rand(i * 3.1 + 5) + travel * (0.5 + rand(i + 9) * 0.7))\nrad = z * z * 1.206\nx = sin(a) * rad / aspect\ny = cos(a) * rad\ntw = 0.4 + 0.6 * sin(z * 40 + time * 3) * (0.5 + treb)\nred = min(1, tw * 1.3)\ngreen = tw * 0.5\nblue = tw * 0.12\nsize = 1 + z * 3 * (0.5 + bass)\nskip = if(above(z, 0.97), 1, 0)\n",
+ 374→ },
+ 375→ {
+ 376→ folder: "root/embedded/Amp Picks",
+ 377→ name: "TC Plasma",
+ 378→ author: "UnConeD",
+ 379→ description: "Auto-ported from the Winamp AVS preset \"UnConeD - TC Plasma.avs\" by UnConeD. The original is driven by CustomBPM, DynamicMovement, FastBright, which the script engine cannot run -- replaced with a generated \"ribbon\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 380→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/UnConeD - TC Plasma.avs",
+ 381→ approx: true,
+ 382→ content: "name: \"TC Plasma\"\nmode: lines\nblend: additive\npersist: 0.932\nlinewidth: 1.976\n\n[init]\nn = 1072\n\n[frame]\nt = t + dt * (0.432 + bass_att * 0.8)\nsway = sway + dt * (0.5 + treb)\nhue = hue + dt * 0.076\n\n[point]\na = i * 6.28318 * 3\nwob = sin(a * 4 + t * 2) * (0.08 + treb * 0.3)\nrad = (0.42 + mid * 0.25) * (0.85 + 0.15 * sin(a * 3 + sway)) + v * 0.223 - wob\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = 0.3 + abs(v) * 1.2 * (0.45 + 0.55 * sin(hue * 6.28318 + a * 0.5))\ngreen = 0.3 + abs(v) * 1.2 * (0.45 + 0.55 * sin(hue * 6.28318 + a * 0.5 + 2.09))\nblue = 0.3 + abs(v) * 1.2 * (0.45 + 0.55 * sin(hue * 6.28318 + a * 0.5 + 4.19))\n",
+ 383→ },
+ 384→ {
+ 385→ folder: "root/embedded/Amp Picks",
+ 386→ name: "Tie Tunnel SSC",
+ 387→ author: "UnConeD",
+ 388→ description: "Auto-ported from the Winamp AVS preset \"UnConeD - Tie Tunnel SSC.avs\" by UnConeD. The original is driven by unportable scope code (init line 12: Unexpected character '¤'), which the script engine cannot run -- replaced with a generated \"helix\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 389→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/UnConeD - Tie Tunnel SSC.avs",
+ 390→ approx: true,
+ 391→ content: "name: \"Tie Tunnel SSC\"\nmode: lines\nblend: additive\npersist: 0.851\nlinewidth: 2.021\n\n[init]\nn = 1179\n\n[frame]\nt = t + dt * (0.17 + bass_att * 0.9)\nhue = hue + dt * 0.129\nflash = flash * 0.86\n\n[beat]\nflash = 1\n\n[point]\nst = floor(i * 2)\nsi = frac(i * 2)\ns = si * 6 * 6.28318 + t\nph = st * 3.14159\nrad = (0.28 + bass * 0.32 + flash * 0.06) * (0.55 + 0.45 * (cos(s + ph) * 0.5 + 0.5))\nx = sin(s + ph) * rad / aspect\ny = (si - 0.5) * 1.8\nred = min(1, 0.25 + 0.55 * (cos(s + ph) * 0.5 + 0.5) + treb * 0.25 * 1.3)\ngreen = 0.25 + 0.55 * (cos(s + ph) * 0.5 + 0.5) + treb * 0.25 * 0.5\nblue = 0.25 + 0.55 * (cos(s + ph) * 0.5 + 0.5) + treb * 0.25 * 0.12\n",
+ 392→ },
+ 393→ {
+ 394→ folder: "root/embedded/Amp Picks",
+ 395→ name: "Zero-G Maze II",
+ 396→ author: "UnConeD",
+ 397→ description: "Auto-ported from the Winamp AVS preset \"UnConeD - Zero-G Maze II.avs\" by UnConeD. The original is driven by DynamicMovement, which the script engine cannot run -- replaced with a generated \"swirl\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 398→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/UnConeD - Zero-G Maze II.avs",
+ 399→ approx: true,
+ 400→ content: "name: \"Zero-G Maze II\"\nmode: lines\nblend: additive\npersist: 0.916\nlinewidth: 1.457\n\n[init]\nn = 1049\n\n[frame]\nt = t + dt * (0.267 + bass_att * 1.8)\nhue = hue + dt * 0.038 + beat * 0.012\n\n[point]\narm = floor(i * 7)\nfa = frac(i * 7)\nd = fa\nang = arm * 1.253 + d * 0.88 * 3.14159 + t\nrad = (0.04 + pow(d, 2.401) * 1.15) * (0.7 + bass * 0.5)\nx = sin(ang) * rad / aspect\ny = cos(ang) * rad\nred = (0.3 + 0.7 * d) * (0.3 + mid) * (0.45 + 0.55 * sin(hue * 6.28318 + d * 2.064))\ngreen = (0.3 + 0.7 * d) * (0.3 + mid) * (0.45 + 0.55 * sin(hue * 6.28318 + d * 2.064 + 2.09))\nblue = (0.3 + 0.7 * d) * (0.3 + mid) * (0.45 + 0.55 * sin(hue * 6.28318 + d * 2.064 + 4.19))\nskip = if(below(fa, 0.04), 1, 0)\n",
+ 401→ },
+ 402→ {
+ 403→ folder: "root/embedded/Amp Picks",
+ 404→ name: "UnConeD -Zero-G Maze III",
+ 405→ author: "",
+ 406→ description: "Auto-ported from the Winamp AVS preset \"UnConeD -Zero-G Maze III.avs\". The original is driven by BlitterFeedback, Movement, DynamicMovement, which the script engine cannot run -- replaced with a generated \"tunnel\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 407→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/UnConeD -Zero-G Maze III.avs",
+ 408→ approx: true,
+ 409→ content: "name: \"UnConeD -Zero-G Maze III\"\nmode: lines\nblend: additive\npersist: 0.853\nlinewidth: 1.507\n\n[init]\nn = 722\n\n[frame]\nt = t + dt * (0.243 + bass_att * 1.5)\nhue = hue + dt * 0.093\n\n[point]\nring = floor(i * 19)\nri2 = frac(i * 19)\nz = frac(ring / 19 + t)\nrad = 0.05 + z * 0.881\na = ri2 * 6.28318 + ring * 0.16 + sin(t * 0.7) * 2\nx = sin(a) * rad * (0.55 + sv * 0.901) / aspect\ny = cos(a) * rad * (0.55 + sv * 0.901)\nred = (1 - z) * (0.3 + bass * 0.7) * 0.3\ngreen = (1 - z) * (0.3 + bass * 0.7) * 0.65\nblue = min(1, (1 - z) * (0.3 + bass * 0.7) * 1.3)\n",
+ 410→ },
+ 411→ {
+ 412→ folder: "root/embedded/Amp Picks",
+ 413→ name: "nebulous (skupers remix)",
+ 414→ author: "amphirion",
+ 415→ description: "Auto-ported from the Winamp AVS preset \"amphirion - nebulous (skupers remix).avs\" by amphirion. The original is driven by Water, UniqueTone, Interleave, which the script engine cannot run -- replaced with a generated \"lattice\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 416→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/amphirion - nebulous (skupers remix).avs",
+ 417→ approx: true,
+ 418→ content: "name: \"nebulous (skupers remix)\"\nmode: dots\nblend: additive\npersist: 0.833\nlinewidth: 2.862\n\n[init]\ncols = 28\nn = cols * cols\n\n[frame]\nrot = rot + dt * (0.104 + mid * 0.4)\nhue = hue + dt * 0.08\n\n[point]\ncx = floor(i * cols) / (cols - 1)\ncy = frac(i * cols)\nh = sv * 0.235\nux = (cx - 0.5) * 1.448\nuy = (cy - 0.5) * 1.448\nrx = ux * cos(rot) - uy * sin(rot)\nry = ux * sin(rot) + uy * cos(rot)\nx = (rx / aspect) * (1 - h * 0.7)\ny = ry * (1 - h * 0.7)\nred = 0.22 + h * 2.2 * (0.45 + 0.55 * sin(hue * 6.28318 + cx * 3.774))\ngreen = 0.22 + h * 2.2 * (0.45 + 0.55 * sin(hue * 6.28318 + cx * 3.774 + 2.09))\nblue = 0.22 + h * 2.2 * (0.45 + 0.55 * sin(hue * 6.28318 + cx * 3.774 + 4.19))\nsize = 1.2 + h * 5\n",
+ 419→ },
+ 420→ {
+ 421→ folder: "root/embedded/Amp Picks",
+ 422→ name: "soundscape III (skupers remix)",
+ 423→ author: "el-vis",
+ 424→ description: "Auto-ported from the Winamp AVS preset \"el-vis - soundscape III (skupers remix).avs\" by el-vis. The original is driven by a trivial scope (the real work is movement/APE driven), which the script engine cannot run -- replaced with a generated \"kaleido\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 425→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/el-vis - soundscape III (skupers remix).avs",
+ 426→ approx: true,
+ 427→ content: "name: \"soundscape III (skupers remix)\"\nmode: lines\nblend: additive\npersist: 0.851\nlinewidth: 1.939\n\n[init]\nsegs = 14\nn = 582\n\n[frame]\nrot = rot + dt * (0.13 + mid_att * 0.5)\nhue = hue + dt * 0.073\nflash = flash * 0.85\n\n[beat]\nflash = 1\n\n[point]\nseg = floor(i * segs)\nsi = frac(i * segs)\na0 = si * 6.28318 / segs + rot\namp = 0.1 + sv * 0.55 + bass * 0.25 + flash * 0.06\nrad = 0.15 + amp * (0.4 + 0.6 * sin(a0 * 4 + time * 2))\na = seg * 6.28318 / segs + a0 * if(below(seg % 2, 1), 1, -1)\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = 0.3 + amp * 1.3 * abs(sin(seg * 0.8))\ngreen = 0.3 + amp * 1.3 * 0.22\nblue = 0.3 + amp * 1.3 * abs(cos(seg * 0.8))\nskip = if(above(si, 0.96), 1, 0)\n",
+ 428→ },
+ 429→ {
+ 430→ folder: "root/embedded/Amp Picks",
+ 431→ name: "checkers with metaballs (skupers remix)",
+ 432→ author: "fck",
+ 433→ description: "Auto-ported from the Winamp AVS preset \"fck - checkers with metaballs (skupers remix).avs\" by fck. Original superscope code, machine-translated to the AVA script dialect.",
+ 434→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/fck - checkers with metaballs (skupers remix).avs",
+ 435→ approx: false,
+ 436→ content: "# Auto-ported from \"fck - checkers with metaballs (skupers remix).avs\" (Winamp AVS / fck)\n# SuperScope point program, 4 AVSTrans macros expanded.\nname: \"checkers with metaballs (skupers remix)\"\nauthor: \"fck\"\nmode: lines\nblend: additive\npersist: 0.88\nlinewidth: 2\nysign: -1\n\n[init]\nn=500;p=3.14159\n\n[frame]\nk=h/w*1.05;mxs=mxs+g;mys=mys+g2;mx=sin(mxs)*.8;my=sin(mys)*.8;t=t+.1\n\n[beat]\ng=getosc(.2,0,0)/3;g2=getosc(.8,0,0)/3\n\n[point]\nd=i/6+getosc(0,0,0)/5*i; \r\nr=i*p*44; \r\nx=(cos(r)*d+mx)*k; \r\ny=sin(r)*d+my;\r\nlig=sin(r)*above(sin(r),0)/2;\r\nblue=(1-i)-lig;\r\ngreen=blue;\r\nred=sqrt(blue)*2-lig\n",
+ 437→ },
+ 438→ {
+ 439→ folder: "root/embedded/Amp Picks",
+ 440→ name: "jinx (skupers remix)",
+ 441→ author: "horse-fly",
+ 442→ description: "Auto-ported from the Winamp AVS preset \"horse-fly - jinx (skupers remix).avs\" by horse-fly. The original is driven by ColorClip, Simple, Mosaic, which the script engine cannot run -- replaced with a generated \"spectrum bars\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 443→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/horse-fly - jinx (skupers remix).avs",
+ 444→ approx: true,
+ 445→ content: "name: \"jinx (skupers remix)\"\nmode: dots\nblend: additive\npersist: 0.814\nlinewidth: 1.769\n\n[init]\nbars = 46\nn = bars * 12\n\n[frame]\nrot = rot + dt * (0.046 + mid * 0.3)\nhue = hue + dt * 0.043\n\n[point]\nbar = floor(i * bars)\nbi = frac(i * bars)\nlvl = getspec(bar / bars, 0, 0)\nh = lvl * 0.65\na = bar * 6.28318 / bars + rot\nrad = 0.246 + bi * h\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = 0.25 + bi * 0.75 * abs(sin(bar * 6.28318 / bars))\ngreen = 0.25 + bi * 0.75 * 0.22\nblue = 0.25 + bi * 0.75 * abs(cos(bar * 6.28318 / bars))\nskip = if(above(bi, 0.97), 1, 0)\n",
+ 446→ },
+ 447→ {
+ 448→ folder: "root/embedded/Amp Picks",
+ 449→ name: "not quite a bendy tunnel (skupers remix)",
+ 450→ author: "jheriko",
+ 451→ description: "Auto-ported from the Winamp AVS preset \"jheriko - not quite a bendy tunnel (skupers remix).avs\" by jheriko. The original is driven by Interleave, DynamicMovement, effect#737, which the script engine cannot run -- replaced with a generated \"lissajous\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 452→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/jheriko - not quite a bendy tunnel (skupers remix).avs",
+ 453→ approx: true,
+ 454→ content: "name: \"not quite a bendy tunnel (skupers remix)\"\nmode: lines\nblend: additive\npersist: 0.862\nlinewidth: 1.443\n\n[init]\nn = 806\n\n[frame]\nt = t + dt * 0.276\nhue = hue + dt * 0.055\n\n[point]\nph = i * 6.28318 * 1.699\nfa = 2 + mid * 0.35\nfb = 3 + bass * 0.45\nx = sin(ph * fa + t) * (0.5 + bass * 0.3) / aspect\ny = sin(ph * fb + t * 1.3 + 2.216) * (0.5 + treb * 0.35)\nred = 0.3 + 0.45 * sin(ph * 2 + t) + abs(v) * 0.6 * 0.3\ngreen = 0.3 + 0.45 * sin(ph * 2 + t) + abs(v) * 0.6 * 0.65\nblue = min(1, 0.3 + 0.45 * sin(ph * 2 + t) + abs(v) * 0.6 * 1.3)\n",
+ 455→ },
+ 456→ {
+ 457→ folder: "root/embedded/Amp Picks",
+ 458→ name: "newpoop (for wa5)",
+ 459→ author: "justin",
+ 460→ description: "Auto-ported from the Winamp AVS preset \"justin - newpoop (for wa5).avs\" by justin. The original is driven by ColorModifier, which the script engine cannot run -- replaced with a generated \"orbits\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 461→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/justin - newpoop (for wa5).avs",
+ 462→ approx: true,
+ 463→ content: "name: \"newpoop (for wa5)\"\nmode: dots\nblend: additive\npersist: 0.796\nlinewidth: 2.44\n\n[init]\nrings = 4\nn = rings * 98\n\n[frame]\nt = t + dt * (0.176 + bass_att * 0.6)\nhue = hue + dt * 0.135\nsquash = 0.549 + 0.25 * sin(t * 0.439)\n\n[point]\nring = floor(i * rings)\nri3 = frac(i * rings)\nr0 = 0.16 + ring * 0.12\na = ri3 * 6.28318 + t * (1.2 - ring * 0.18) + ring * 2.4\nx = sin(a) * r0 * (1 + bass * 0.18) / aspect\ny = cos(a) * r0 * squash\nsat = frac(ri3 * 5)\nsize = if(below(sat, 0.14), 3 + bass * 3, 1)\nred = min(1, 0.25 + bass * 0.5 + ring * 0.07 * 1.3)\ngreen = 0.25 + bass * 0.5 + ring * 0.07 * 0.5\nblue = 0.25 + bass * 0.5 + ring * 0.07 * 0.12\n",
+ 464→ },
+ 465→ {
+ 466→ folder: "root/embedded/Amp Picks",
+ 467→ name: "the landscape is killing me (wa5remix)",
+ 468→ author: "justin",
+ 469→ description: "Auto-ported from the Winamp AVS preset \"justin - the landscape is killing me (wa5remix).avs\" by justin. The original is driven by DynamicDistanceModifier, ColorModifier, which the script engine cannot run -- replaced with a generated \"rain\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 470→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/justin - the landscape is killing me (wa5remix).avs",
+ 471→ approx: true,
+ 472→ content: "name: \"the landscape is killing me (wa5remix)\"\nmode: dots\nblend: additive\npersist: 0.704\nlinewidth: 2.586\n\n[init]\ncols = 54\nn = 1193\n\n[frame]\nfall = fall + dt * (0.514 + vol_att * 0.841)\nhue = hue + dt * 0.071\n\n[point]\ncol = floor(i * cols)\nci = frac(i * cols)\nxx = (col / cols - 0.5) * 1.9\nyf = frac(rand(col * 3.7 + 1) + fall * (0.55 + rand(col + 5) * 0.5))\ntail = rand(col * 1.3 + 9) * 0.31 + 0.04\nyy = (yf * 2 - 1) * 1.05 + ci * tail\nx = xx / aspect\ny = yy\nred = (1 - ci) * (0.2 + treb * 0.85) + 0.04 * (0.45 + 0.55 * sin(hue * 6.28318 + col * 0.3))\ngreen = (1 - ci) * (0.2 + treb * 0.85) + 0.04 * (0.45 + 0.55 * sin(hue * 6.28318 + col * 0.3 + 2.09))\nblue = (1 - ci) * (0.2 + treb * 0.85) + 0.04 * (0.45 + 0.55 * sin(hue * 6.28318 + col * 0.3 + 4.19))\nskip = if(above(abs(yy), 1.02), 1, 0)\n",
+ 473→ },
+ 474→ {
+ 475→ folder: "root/embedded/Amp Picks",
+ 476→ name: "Dragonscales",
+ 477→ author: "mig",
+ 478→ description: "Auto-ported from the Winamp AVS preset \"mig - Dragonscales.avs\" by mig. The original is driven by CustomBPM, BufferSave, MovingParticle, which the script engine cannot run -- replaced with a generated \"lissajous\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 479→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/mig - Dragonscales.avs",
+ 480→ approx: true,
+ 481→ content: "name: \"Dragonscales\"\nmode: lines\nblend: additive\npersist: 0.885\nlinewidth: 2.375\n\n[init]\nn = 938\n\n[frame]\nt = t + dt * 0.353\nhue = hue + dt * 0.094\n\n[point]\nph = i * 6.28318 * 2.096\nfa = 2 + mid * 0.35\nfb = 3 + bass * 0.45\nx = sin(ph * fa + t) * (0.5 + bass * 0.3) / aspect\ny = sin(ph * fb + t * 1.3 + 2.866) * (0.5 + treb * 0.35)\nred = 0.3 + 0.45 * sin(ph * 2 + t) + abs(v) * 0.6 * (0.25 + bass * 0.95)\ngreen = 0.3 + 0.45 * sin(ph * 2 + t) + abs(v) * 0.6 * (0.25 + mid * 0.95)\nblue = 0.3 + 0.45 * sin(ph * 2 + t) + abs(v) * 0.6 * (0.25 + treb * 0.95)\n",
+ 482→ },
+ 483→ {
+ 484→ folder: "root/embedded/Amp Picks",
+ 485→ name: "Encapsulade",
+ 486→ author: "mig",
+ 487→ description: "Auto-ported from the Winamp AVS preset \"mig - Encapsulade.avs\" by mig. The original is driven by BufferSave, SetRenderMode, CustomBPM, which the script engine cannot run -- replaced with a generated \"spectrum bars\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 488→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/mig - Encapsulade.avs",
+ 489→ approx: true,
+ 490→ content: "name: \"Encapsulade\"\nmode: lines\nblend: additive\npersist: 0.814\nlinewidth: 2.058\n\n[init]\nbars = 64\nn = bars * 9\n\n[frame]\nrot = rot + dt * (0.103 + mid * 0.3)\nhue = hue + dt * 0.072\n\n[point]\nbar = floor(i * bars)\nbi = frac(i * bars)\nlvl = getspec(bar / bars, 0, 0)\nh = lvl * 0.85\na = bar * 6.28318 / bars + rot\nrad = 0.262 + bi * h\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = 0.25 + bi * 0.75 * (0.45 + 0.55 * sin(hue * 6.28318 + bar * 6.28318 / bars))\ngreen = 0.25 + bi * 0.75 * (0.45 + 0.55 * sin(hue * 6.28318 + bar * 6.28318 / bars + 2.09))\nblue = 0.25 + bi * 0.75 * (0.45 + 0.55 * sin(hue * 6.28318 + bar * 6.28318 / bars + 4.19))\nskip = if(above(bi, 0.97), 1, 0)\n",
+ 491→ },
+ 492→ {
+ 493→ folder: "root/embedded/Amp Picks",
+ 494→ name: "Freekwyrms",
+ 495→ author: "mig",
+ 496→ description: "Auto-ported from the Winamp AVS preset \"mig - Freekwyrms.avs\" by mig. The original is driven by BufferSave, SetRenderMode, MovingParticle, which the script engine cannot run -- replaced with a generated \"spectrum bars\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 497→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/mig - Freekwyrms.avs",
+ 498→ approx: true,
+ 499→ content: "name: \"Freekwyrms\"\nmode: dots\nblend: additive\npersist: 0.864\nlinewidth: 1.436\n\n[init]\nbars = 46\nn = bars * 12\n\n[frame]\nrot = rot + dt * (0.09 + mid * 0.3)\nhue = hue + dt * 0.063\n\n[point]\nbar = floor(i * bars)\nbi = frac(i * bars)\nlvl = getspec(bar / bars, 0, 0)\nh = lvl * 0.722\na = bar * 6.28318 / bars + rot\nrad = 0.216 + bi * h\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = min(1, 0.25 + bi * 0.75 * 1.3)\ngreen = 0.25 + bi * 0.75 * 0.5\nblue = 0.25 + bi * 0.75 * 0.12\nskip = if(above(bi, 0.97), 1, 0)\n",
+ 500→ },
+ 501→ {
+ 502→ folder: "root/embedded/Amp Picks",
+ 503→ name: "Lilypod",
+ 504→ author: "mig",
+ 505→ description: "Auto-ported from the Winamp AVS preset \"mig - Lilypod.avs\" by mig. The original is driven by BufferSave, MovingParticle, 2.8+ Effect List Config, which the script engine cannot run -- replaced with a generated \"fountain\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 506→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/mig - Lilypod.avs",
+ 507→ approx: true,
+ 508→ content: "name: \"Lilypod\"\nmode: dots\nblend: additive\npersist: 0.787\nlinewidth: 1.928\n\n[init]\nn = 885\n\n[frame]\ntt = tt + dt * (0.5 + bass_att * 1.1)\nhue = hue + dt * 0.04\nburst = burst * 0.9\n\n[beat]\nburst = 1\n\n[point]\njet = floor(i * 4)\nlife = frac(rand(i * 7.7 + jet) + tt * (0.35 + rand(i + 3) * 0.65))\na = jet * 6.28318 / 4 + rand(i * 5.1 + jet) * 0.722\nspd = 0.4 + rand(i * 9.2 + jet) * 0.8\npx = cos(a) * spd * life\npy = sin(a) * spd * life - 0.744 * life * life\nx = px * (0.85 + bass * 0.35) / aspect\ny = py\nred = max(0, 1 - life) * (0.35 + burst * 0.55) * abs(sin(life * 4))\ngreen = max(0, 1 - life) * (0.35 + burst * 0.55) * 0.22\nblue = max(0, 1 - life) * (0.35 + burst * 0.55) * abs(cos(life * 4))\nskip = if(above(life, 0.94), 1, 0)\n",
+ 509→ },
+ 510→ {
+ 511→ folder: "root/embedded/Amp Picks",
+ 512→ name: "Mist Wisps",
+ 513→ author: "mig",
+ 514→ description: "Auto-ported from the Winamp AVS preset \"mig - Mist Wisps.avs\" by mig. The original is driven by CustomBPM, BufferSave, MovingParticle, which the script engine cannot run -- replaced with a generated \"moire\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 515→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/mig - Mist Wisps.avs",
+ 516→ approx: true,
+ 517→ content: "name: \"Mist Wisps\"\nmode: dots\nblend: additive\npersist: 0.758\nlinewidth: 2.24\n\n[init]\nn = 1297\n\n[frame]\nt = t + dt * 0.323\nhue = hue + dt * 0.1\n\n[point]\nu = rand(i * 1.7) * 2 - 1\nw = rand(i * 3.3 + 11) * 2 - 1\nm = sin((u * 2.207 + w * 6.547 + t) * 3.14159) * sin((u * 5.739 - w * 2.319 - t * 0.7) * 3.14159)\nx = u * 0.85 / aspect\ny = w * 0.957\nlum = max(0, m) * (0.3 + bass * 0.9) + 0.06\nred = lum * (0.25 + bass * 0.95)\ngreen = lum * (0.25 + mid * 0.95)\nblue = lum * (0.25 + treb * 0.95)\nsize = 1 + abs(m) * 3.505\n",
+ 518→ },
+ 519→ {
+ 520→ folder: "root/embedded/Amp Picks",
+ 521→ name: "Nebulating - Hypercolorspace",
+ 522→ author: "mig",
+ 523→ description: "Auto-ported from the Winamp AVS preset \"mig - Nebulating - Hypercolorspace.avs\" by mig. The original is driven by CustomBPM, BufferSave, Fadeout, which the script engine cannot run -- replaced with a generated \"moire\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 524→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/mig - Nebulating - Hypercolorspace.avs",
+ 525→ approx: true,
+ 526→ content: "name: \"Nebulating - Hypercolorspace\"\nmode: dots\nblend: additive\npersist: 0.792\nlinewidth: 2.678\n\n[init]\nn = 1374\n\n[frame]\nt = t + dt * 0.423\nhue = hue + dt * 0.123\n\n[point]\nu = rand(i * 1.7) * 2 - 1\nw = rand(i * 3.3 + 11) * 2 - 1\nm = sin((u * 3.51 + w * 2.666 + t) * 3.14159) * sin((u * 6.323 - w * 6.393 - t * 0.7) * 3.14159)\nx = u * 0.959 / aspect\ny = w * 0.871\nlum = max(0, m) * (0.3 + bass * 0.9) + 0.06\nred = min(1, lum * 1.3)\ngreen = lum * 0.5\nblue = lum * 0.12\nsize = 1 + abs(m) * 3.799\n",
+ 527→ },
+ 528→ {
+ 529→ folder: "root/embedded/Amp Picks",
+ 530→ name: "Reverb - Deep Net",
+ 531→ author: "mig",
+ 532→ description: "Auto-ported from the Winamp AVS preset \"mig - Reverb - Deep Net.avs\" by mig. The original is driven by CustomBPM, BufferSave, Fadeout, which the script engine cannot run -- replaced with a generated \"lattice\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 533→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/mig - Reverb - Deep Net.avs",
+ 534→ approx: true,
+ 535→ content: "name: \"Reverb - Deep Net\"\nmode: dots\nblend: additive\npersist: 0.818\nlinewidth: 2.394\n\n[init]\ncols = 29\nn = cols * cols\n\n[frame]\nrot = rot + dt * (0.041 + mid * 0.4)\nhue = hue + dt * 0.089\n\n[point]\ncx = floor(i * cols) / (cols - 1)\ncy = frac(i * cols)\nh = sv * 0.525\nux = (cx - 0.5) * 1.845\nuy = (cy - 0.5) * 1.845\nrx = ux * cos(rot) - uy * sin(rot)\nry = ux * sin(rot) + uy * cos(rot)\nx = (rx / aspect) * (1 - h * 0.7)\ny = ry * (1 - h * 0.7)\nred = 0.22 + h * 2.2 * abs(sin(cx * 2.712))\ngreen = 0.22 + h * 2.2 * 0.22\nblue = 0.22 + h * 2.2 * abs(cos(cx * 2.712))\nsize = 1.2 + h * 5\n",
+ 536→ },
+ 537→ {
+ 538→ folder: "root/embedded/Amp Picks",
+ 539→ name: "Slyde - Tri",
+ 540→ author: "mig",
+ 541→ description: "Auto-ported from the Winamp AVS preset \"mig - Slyde - Tri.avs\" by mig. The original is driven by BufferSave, SetRenderMode, MovingParticle, which the script engine cannot run -- replaced with a generated \"lissajous\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 542→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/mig - Slyde - Tri.avs",
+ 543→ approx: true,
+ 544→ content: "name: \"Slyde - Tri\"\nmode: lines\nblend: additive\npersist: 0.863\nlinewidth: 1.626\n\n[init]\nn = 938\n\n[frame]\nt = t + dt * 0.483\nhue = hue + dt * 0.076\n\n[point]\nph = i * 6.28318 * 2.841\nfa = 6 + mid * 0.35\nfb = 2 + bass * 0.45\nx = sin(ph * fa + t) * (0.5 + bass * 0.3) / aspect\ny = sin(ph * fb + t * 1.3 + 1.909) * (0.5 + treb * 0.35)\nred = min(1, 0.3 + 0.45 * sin(ph * 2 + t) + abs(v) * 0.6 * 1.3)\ngreen = 0.3 + 0.45 * sin(ph * 2 + t) + abs(v) * 0.6 * 0.5\nblue = 0.3 + 0.45 * sin(ph * 2 + t) + abs(v) * 0.6 * 0.12\n",
+ 545→ },
+ 546→ {
+ 547→ folder: "root/embedded/Amp Picks",
+ 548→ name: "Solar Flaire",
+ 549→ author: "mig",
+ 550→ description: "Auto-ported from the Winamp AVS preset \"mig - Solar Flaire.avs\" by mig. The original is driven by BufferSave, Fadeout, SetRenderMode, which the script engine cannot run -- replaced with a generated \"spectrum bars\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 551→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/mig - Solar Flaire.avs",
+ 552→ approx: true,
+ 553→ content: "name: \"Solar Flaire\"\nmode: lines\nblend: additive\npersist: 0.866\nlinewidth: 1.678\n\n[init]\nbars = 59\nn = bars * 11\n\n[frame]\nrot = rot + dt * (0.076 + mid * 0.3)\nhue = hue + dt * 0.107\n\n[point]\nbar = floor(i * bars)\nbi = frac(i * bars)\nlvl = getspec(bar / bars, 0, 0)\nh = lvl * 0.607\na = bar * 6.28318 / bars + rot\nrad = 0.241 + bi * h\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = 0.25 + bi * 0.75 * (0.45 + 0.55 * sin(hue * 6.28318 + bar * 6.28318 / bars))\ngreen = 0.25 + bi * 0.75 * (0.45 + 0.55 * sin(hue * 6.28318 + bar * 6.28318 / bars + 2.09))\nblue = 0.25 + bi * 0.75 * (0.45 + 0.55 * sin(hue * 6.28318 + bar * 6.28318 / bars + 4.19))\nskip = if(above(bi, 0.97), 1, 0)\n",
+ 554→ },
+ 555→ {
+ 556→ folder: "root/embedded/Amp Picks",
+ 557→ name: "Spintronics - Backspin",
+ 558→ author: "mig",
+ 559→ description: "Auto-ported from the Winamp AVS preset \"mig - Spintronics - Backspin.avs\" by mig. The original is driven by 2.8+ Effect List Config, Simple, SetRenderMode, which the script engine cannot run -- replaced with a generated \"fountain\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 560→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/mig - Spintronics - Backspin.avs",
+ 561→ approx: true,
+ 562→ content: "name: \"Spintronics - Backspin\"\nmode: dots\nblend: additive\npersist: 0.804\nlinewidth: 2.037\n\n[init]\nn = 1157\n\n[frame]\ntt = tt + dt * (0.5 + bass_att * 1.1)\nhue = hue + dt * 0.12\nburst = burst * 0.9\n\n[beat]\nburst = 1\n\n[point]\njet = floor(i * 4)\nlife = frac(rand(i * 7.7 + jet) + tt * (0.35 + rand(i + 3) * 0.65))\na = jet * 6.28318 / 4 + rand(i * 5.1 + jet) * 0.547\nspd = 0.4 + rand(i * 9.2 + jet) * 0.8\npx = cos(a) * spd * life\npy = sin(a) * spd * life - 0.712 * life * life\nx = px * (0.85 + bass * 0.35) / aspect\ny = py\nred = min(1, max(0, 1 - life) * (0.35 + burst * 0.55) * 1.3)\ngreen = max(0, 1 - life) * (0.35 + burst * 0.55) * 0.5\nblue = max(0, 1 - life) * (0.35 + burst * 0.55) * 0.12\nskip = if(above(life, 0.94), 1, 0)\n",
+ 563→ },
+ 564→ {
+ 565→ folder: "root/embedded/Amp Picks",
+ 566→ name: "Starslappin",
+ 567→ author: "mig",
+ 568→ description: "Auto-ported from the Winamp AVS preset \"mig - Starslappin.avs\" by mig. The original is driven by unportable scope code (frame line 20: Expected ')' but found ';'), which the script engine cannot run -- replaced with a generated \"kaleido\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 569→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/mig - Starslappin.avs",
+ 570→ approx: true,
+ 571→ content: "name: \"Starslappin\"\nmode: lines\nblend: additive\npersist: 0.842\nlinewidth: 2.148\n\n[init]\nsegs = 7\nn = 545\n\n[frame]\nrot = rot + dt * (0.087 + mid_att * 0.5)\nhue = hue + dt * 0.125\nflash = flash * 0.85\n\n[beat]\nflash = 1\n\n[point]\nseg = floor(i * segs)\nsi = frac(i * segs)\na0 = si * 6.28318 / segs + rot\namp = 0.1 + sv * 0.55 + bass * 0.25 + flash * 0.06\nrad = 0.15 + amp * (0.4 + 0.6 * sin(a0 * 6 + time * 2))\na = seg * 6.28318 / segs + a0 * if(below(seg % 2, 1), 1, -1)\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = 0.3 + amp * 1.3 * (0.25 + bass * 0.95)\ngreen = 0.3 + amp * 1.3 * (0.25 + mid * 0.95)\nblue = 0.3 + amp * 1.3 * (0.25 + treb * 0.95)\nskip = if(above(si, 0.96), 1, 0)\n",
+ 572→ },
+ 573→ {
+ 574→ folder: "root/embedded/Amp Picks",
+ 575→ name: "Street Art - Fire Inside",
+ 576→ author: "mig",
+ 577→ description: "Auto-ported from the Winamp AVS preset \"mig - Street Art - Fire Inside.avs\" by mig. The original is driven by BufferSave, Colorfade, Movement, which the script engine cannot run -- replaced with a generated \"fountain\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 578→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/mig - Street Art - Fire Inside.avs",
+ 579→ approx: true,
+ 580→ content: "name: \"Street Art - Fire Inside\"\nmode: dots\nblend: additive\npersist: 0.757\nlinewidth: 2.052\n\n[init]\nn = 1065\n\n[frame]\ntt = tt + dt * (0.5 + bass_att * 1.1)\nhue = hue + dt * 0.108\nburst = burst * 0.9\n\n[beat]\nburst = 1\n\n[point]\njet = floor(i * 4)\nlife = frac(rand(i * 7.7 + jet) + tt * (0.35 + rand(i + 3) * 0.65))\na = jet * 6.28318 / 4 + rand(i * 5.1 + jet) * 1.184\nspd = 0.4 + rand(i * 9.2 + jet) * 0.8\npx = cos(a) * spd * life\npy = sin(a) * spd * life - 1.383 * life * life\nx = px * (0.85 + bass * 0.35) / aspect\ny = py\nred = max(0, 1 - life) * (0.35 + burst * 0.55) * 0.3\ngreen = max(0, 1 - life) * (0.35 + burst * 0.55) * 0.65\nblue = min(1, max(0, 1 - life) * (0.35 + burst * 0.55) * 1.3)\nskip = if(above(life, 0.94), 1, 0)\n",
+ 581→ },
+ 582→ {
+ 583→ folder: "root/embedded/Amp Picks",
+ 584→ name: "Swoo - Slow",
+ 585→ author: "mig",
+ 586→ description: "Auto-ported from the Winamp AVS preset \"mig - Swoo - Slow.avs\" by mig. The original is driven by CustomBPM, BufferSave, Movement, which the script engine cannot run -- replaced with a generated \"ribbon\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 587→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/mig - Swoo - Slow.avs",
+ 588→ approx: true,
+ 589→ content: "name: \"Swoo - Slow\"\nmode: lines\nblend: additive\npersist: 0.919\nlinewidth: 1.852\n\n[init]\nn = 1159\n\n[frame]\nt = t + dt * (0.461 + bass_att * 0.8)\nsway = sway + dt * (0.5 + treb)\nhue = hue + dt * 0.046\n\n[point]\na = i * 6.28318 * 3\nwob = sin(a * 3 + t * 2) * (0.08 + treb * 0.3)\nrad = (0.42 + mid * 0.25) * (0.85 + 0.15 * sin(a * 3 + sway)) + v * 0.199 - wob\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = 0.3 + abs(v) * 1.2 * 0.3\ngreen = 0.3 + abs(v) * 1.2 * 0.65\nblue = min(1, 0.3 + abs(v) * 1.2 * 1.3)\n",
+ 590→ },
+ 591→ {
+ 592→ folder: "root/embedded/Amp Picks",
+ 593→ name: "smell of music (skupers remix)",
+ 594→ author: "nixa",
+ 595→ description: "Auto-ported from the Winamp AVS preset \"nixa - smell of music (skupers remix).avs\" by nixa. The original is driven by Mirror, DynamicMovement, Movement, which the script engine cannot run -- replaced with a generated \"web\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 596→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/nixa - smell of music (skupers remix).avs",
+ 597→ approx: true,
+ 598→ content: "name: \"smell of music (skupers remix)\"\nmode: lines\nblend: additive\npersist: 0.805\nlinewidth: 1.342\n\n[init]\nspokes = 31\nn = spokes * 34\n\n[frame]\nrot = rot + dt * (0.118 + mid * 0.4)\nt = t + dt * 2.453\nhue = hue + dt * 0.113\n\n[point]\nsp = floor(i * spokes)\nsi = frac(i * spokes)\na = sp * 6.28318 / spokes + rot\nwave = sin(si * 3 * 3.14159 - t) * (0.03 + bass * 0.28)\nrad = 0.12 + si * 0.868 + wave\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = (1 - si) * 0.45 + 0.15 + sv * 0.55 * 0.3\ngreen = (1 - si) * 0.45 + 0.15 + sv * 0.55 * 0.65\nblue = min(1, (1 - si) * 0.45 + 0.15 + sv * 0.55 * 1.3)\nskip = if(above(si, 0.985), 1, 0)\n",
+ 599→ },
+ 600→ {
+ 601→ folder: "root/embedded/Amp Picks",
+ 602→ name: "bumpy",
+ 603→ author: "p-k",
+ 604→ description: "Auto-ported from the Winamp AVS preset \"p-k - bumpy.avs\" by p-k. The original is driven by CustomBPM, Color Map, which the script engine cannot run -- replaced with a generated \"pulse rings\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 605→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/p-k - bumpy.avs",
+ 606→ approx: true,
+ 607→ content: "name: \"bumpy\"\nmode: dots\nblend: additive\npersist: 0.798\nlinewidth: 1.642\n\n[init]\nn = 640\n\n[frame]\nhue = hue + dt * 0.119 + beat * 0.01\ntravel = travel + dt * (0.394 + bass_att * 1.4)\nflash = flash * 0.86\n\n[beat]\nflash = 1\n\n[point]\ng = floor(i * 10)\ngi = frac(i * 10)\nph = frac(rand(g * 5.1 + 1) + travel * (0.55 + rand(g + 17) * 0.85))\na = gi * 6.28318\nrad = ph * (0.28 + bass * 0.544 + flash * 0.08)\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nfade = sin(ph * 3.14159)\nred = fade * (0.35 + flash * 0.4) * 0.3\ngreen = fade * (0.35 + flash * 0.4) * 0.65\nblue = min(1, fade * (0.35 + flash * 0.4) * 1.3)\nskip = if(above(ph, 0.96), 1, 0)\n",
+ 608→ },
+ 609→ {
+ 610→ folder: "root/embedded/Amp Picks",
+ 611→ name: "splendora",
+ 612→ author: "",
+ 613→ description: "Auto-ported from the Winamp AVS preset \"splendora.avs\". The original is driven by Grain, Simple, Movement, which the script engine cannot run -- replaced with a generated \"swirl\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 614→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/splendora.avs",
+ 615→ approx: true,
+ 616→ content: "name: \"splendora\"\nmode: lines\nblend: additive\npersist: 0.867\nlinewidth: 1.513\n\n[init]\nn = 974\n\n[frame]\nt = t + dt * (0.211 + bass_att * 1.8)\nhue = hue + dt * 0.068 + beat * 0.012\n\n[point]\narm = floor(i * 9)\nfa = frac(i * 9)\nd = fa\nang = arm * 1.515 + d * 1.922 * 3.14159 + t\nrad = (0.04 + pow(d, 1.746) * 1.15) * (0.7 + bass * 0.5)\nx = sin(ang) * rad / aspect\ny = cos(ang) * rad\nred = (0.3 + 0.7 * d) * (0.3 + mid) * abs(sin(d * 2.585))\ngreen = (0.3 + 0.7 * d) * (0.3 + mid) * 0.22\nblue = (0.3 + 0.7 * d) * (0.3 + mid) * abs(cos(d * 2.585))\nskip = if(below(fa, 0.04), 1, 0)\n",
+ 617→ },
+ 618→ {
+ 619→ folder: "root/embedded/Amp Picks",
+ 620→ name: "Urantia",
+ 621→ author: "zxe",
+ 622→ description: "Auto-ported from the Winamp AVS preset \"zxe-Urantia.avs\" by zxe. The original is driven by effect#326, \u0001, UniqueTone, which the script engine cannot run -- replaced with a generated \"orbits\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 623→ originalFile: "/avs-originals/embedded/root/embedded/Amp Picks/zxe-Urantia.avs",
+ 624→ approx: true,
+ 625→ content: "name: \"Urantia\"\nmode: dots\nblend: additive\npersist: 0.862\nlinewidth: 2.9\n\n[init]\nrings = 4\nn = rings * 101\n\n[frame]\nt = t + dt * (0.393 + bass_att * 0.6)\nhue = hue + dt * 0.094\nsquash = 0.387 + 0.25 * sin(t * 0.454)\n\n[point]\nring = floor(i * rings)\nri3 = frac(i * rings)\nr0 = 0.16 + ring * 0.141\na = ri3 * 6.28318 + t * (1.2 - ring * 0.18) + ring * 2.4\nx = sin(a) * r0 * (1 + bass * 0.18) / aspect\ny = cos(a) * r0 * squash\nsat = frac(ri3 * 2)\nsize = if(below(sat, 0.14), 3 + bass * 3, 1)\nred = 0.25 + bass * 0.5 + ring * 0.07 * (0.25 + bass * 0.95)\ngreen = 0.25 + bass * 0.5 + ring * 0.07 * (0.25 + mid * 0.95)\nblue = 0.25 + bass * 0.5 + ring * 0.07 * (0.25 + treb * 0.95)\n",
+ 626→ },
+ 627→ {
+ 628→ folder: "root/embedded/Dynamics",
+ 629→ name: "tentaclism (fake hboy rmx)",
+ 630→ author: "dynamic duo",
+ 631→ description: "Auto-ported from the Winamp AVS preset \"01 dynamic duo - tentaclism (fake hboy rmx).avs\" by dynamic duo. The original is driven by unportable scope code (frame line 20: Expected ')' but found ';'), which the script engine cannot run -- replaced with a generated \"starfield\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 632→ originalFile: "/avs-originals/embedded/root/embedded/Dynamics/01 dynamic duo - tentaclism (fake hboy rmx).avs",
+ 633→ approx: true,
+ 634→ content: "name: \"tentaclism (fake hboy rmx)\"\nmode: dots\nblend: additive\npersist: 0.833\nlinewidth: 2.485\n\n[init]\nn = 952\n\n[frame]\ntravel = travel + dt * (0.231 + vol_att * 1.898)\nhue = hue + dt * 0.056\n\n[point]\na = rand(i * 7.3) * 6.28318\nz = frac(rand(i * 3.1 + 5) + travel * (0.5 + rand(i + 9) * 0.7))\nrad = z * z * 0.992\nx = sin(a) * rad / aspect\ny = cos(a) * rad\ntw = 0.4 + 0.6 * sin(z * 40 + time * 3) * (0.5 + treb)\nred = min(1, tw * 1.3)\ngreen = tw * 0.5\nblue = tw * 0.12\nsize = 1 + z * 3 * (0.5 + bass)\nskip = if(above(z, 0.97), 1, 0)\n",
+ 635→ },
+ 636→ {
+ 637→ folder: "root/embedded/Dynamics",
+ 638→ name: "tentaclism (sky of contempt mix by drew)",
+ 639→ author: "dynamic duo",
+ 640→ description: "Auto-ported from the Winamp AVS preset \"02 dynamic duo - tentaclism (sky of contempt mix by drew).avs\" by dynamic duo. The original is driven by unportable scope code (frame line 20: Expected ')' but found ';'), which the script engine cannot run -- replaced with a generated \"orbits\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 641→ originalFile: "/avs-originals/embedded/root/embedded/Dynamics/02 dynamic duo - tentaclism (sky of contempt mix by drew).avs",
+ 642→ approx: true,
+ 643→ content: "name: \"tentaclism (sky of contempt mix by drew)\"\nmode: dots\nblend: additive\npersist: 0.83\nlinewidth: 2.652\n\n[init]\nrings = 3\nn = rings * 87\n\n[frame]\nt = t + dt * (0.201 + bass_att * 0.6)\nhue = hue + dt * 0.06\nsquash = 0.564 + 0.25 * sin(t * 0.401)\n\n[point]\nring = floor(i * rings)\nri3 = frac(i * rings)\nr0 = 0.16 + ring * 0.108\na = ri3 * 6.28318 + t * (1.2 - ring * 0.18) + ring * 2.4\nx = sin(a) * r0 * (1 + bass * 0.18) / aspect\ny = cos(a) * r0 * squash\nsat = frac(ri3 * 4)\nsize = if(below(sat, 0.14), 3 + bass * 3, 1)\nred = 0.25 + bass * 0.5 + ring * 0.07 * abs(sin(ring * 1.3))\ngreen = 0.25 + bass * 0.5 + ring * 0.07 * 0.22\nblue = 0.25 + bass * 0.5 + ring * 0.07 * abs(cos(ring * 1.3))\n",
+ 644→ },
+ 645→ {
+ 646→ folder: "root/embedded/Dynamics",
+ 647→ name: "tentaclism (microdee's grandpa was a tentaclist)",
+ 648→ author: "dynamic duo",
+ 649→ description: "Auto-ported from the Winamp AVS preset \"03 dynamic duo - tentaclism (microdee's grandpa was a tentaclist).avs\" by dynamic duo. Original superscope code, machine-translated to the AVA script dialect.",
+ 650→ originalFile: "/avs-originals/embedded/root/embedded/Dynamics/03 dynamic duo - tentaclism (microdee's grandpa was a tentaclist).avs",
+ 651→ approx: false,
+ 652→ content: "# Auto-ported from \"03 dynamic duo - tentaclism (microdee's grandpa was a tentaclist).avs\" (Winamp AVS / dynamic duo)\n# SuperScope point program, 4 AVSTrans macros expanded.\nname: \"tentaclism (microdee's grandpa was a tentaclist)\"\nauthor: \"dynamic duo\"\nmode: dots\nblend: additive\npersist: 0.78\nlinewidth: 3\nysign: -1\n\n[init]\nn=40; ln1=1; ln=ln1+2;\r\ncns=.6; chs2=200; chs1=35; mrab=rnd(10)/20;\n\n[frame]\nt=t-0.05; pc=0; sw=-1; sm=sm+rnd(20)/200;\r\nsrm=.9*(srm+rnd(100)*.0004);\r\nssm=sin(sm)*srm+rnd(100)*.0003;\n\n[beat]\nflash = 1\n\n[point]\nsw=sw*-1;\r\nx=if(above(pc,ln),rnd(100)/50-1,rnd(100)/2000+.27+ssm);y=if(above(pc,ln),rnd(100)/50-1,sw);\r\nred=rnd(100)/100; green=red; blue=red; pc=pc+1;\r\ndrawmode=below(pc,ln); skip=band(below(pc,ln),equal(sw,1))\n",
+ 653→ },
+ 654→ {
+ 655→ folder: "root/embedded/Dynamics",
+ 656→ name: "tentaclism (cathode ray mix by onionring of doom)",
+ 657→ author: "dynamic duo",
+ 658→ description: "Auto-ported from the Winamp AVS preset \"04 dynamic duo - tentaclism (cathode ray mix by onionring of doom).avs\" by dynamic duo. The original is driven by unportable scope code (frame line 19: Expected ')' but found ';'), which the script engine cannot run -- replaced with a generated \"web\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 659→ originalFile: "/avs-originals/embedded/root/embedded/Dynamics/04 dynamic duo - tentaclism (cathode ray mix by onionring of doom).avs",
+ 660→ approx: true,
+ 661→ content: "name: \"tentaclism (cathode ray mix by onionring of doom)\"\nmode: lines\nblend: additive\npersist: 0.873\nlinewidth: 1.987\n\n[init]\nspokes = 26\nn = spokes * 38\n\n[frame]\nrot = rot + dt * (0.076 + mid * 0.4)\nt = t + dt * 1.652\nhue = hue + dt * 0.062\n\n[point]\nsp = floor(i * spokes)\nsi = frac(i * spokes)\na = sp * 6.28318 / spokes + rot\nwave = sin(si * 5 * 3.14159 - t) * (0.03 + bass * 0.28)\nrad = 0.12 + si * 0.882 + wave\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = (1 - si) * 0.45 + 0.15 + sv * 0.55 * 0.3\ngreen = (1 - si) * 0.45 + 0.15 + sv * 0.55 * 0.65\nblue = min(1, (1 - si) * 0.45 + 0.15 + sv * 0.55 * 1.3)\nskip = if(above(si, 0.985), 1, 0)\n",
+ 662→ },
+ 663→ {
+ 664→ folder: "root/embedded/Dynamics",
+ 665→ name: "chromatics (glowy mix by meaningless-existance)",
+ 666→ author: "dynamic duo",
+ 667→ description: "Auto-ported from the Winamp AVS preset \"05 dynamic duo - chromatics (glowy mix by meaningless-existance).avs\" by dynamic duo. The original is driven by a trivial scope (n=4; the real work is movement/APE driven), which the script engine cannot run -- replaced with a generated \"tunnel\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 668→ originalFile: "/avs-originals/embedded/root/embedded/Dynamics/05 dynamic duo - chromatics (glowy mix by meaningless-existance).avs",
+ 669→ approx: true,
+ 670→ content: "name: \"chromatics (glowy mix by meaningless-existance)\"\nmode: lines\nblend: additive\npersist: 0.857\nlinewidth: 1.543\n\n[init]\nn = 731\n\n[frame]\nt = t + dt * (0.338 + bass_att * 1.5)\nhue = hue + dt * 0.042\n\n[point]\nring = floor(i * 17)\nri2 = frac(i * 17)\nz = frac(ring / 17 + t)\nrad = 0.05 + z * 0.805\na = ri2 * 6.28318 + ring * 0.411 + sin(t * 0.7) * 2\nx = sin(a) * rad * (0.55 + sv * 1.073) / aspect\ny = cos(a) * rad * (0.55 + sv * 1.073)\nred = (1 - z) * (0.3 + bass * 0.7) * abs(sin(z * 2.074))\ngreen = (1 - z) * (0.3 + bass * 0.7) * 0.22\nblue = (1 - z) * (0.3 + bass * 0.7) * abs(cos(z * 2.074))\n",
+ 671→ },
+ 672→ {
+ 673→ folder: "root/embedded/Dynamics",
+ 674→ name: "tentaclism (midas remix)",
+ 675→ author: "dynamic duo",
+ 676→ description: "Auto-ported from the Winamp AVS preset \"06 dynamic duo - tentaclism (midas remix).avs\" by dynamic duo. The original is driven by unportable scope code (frame line 20: Expected ')' but found ';'), which the script engine cannot run -- replaced with a generated \"fountain\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 677→ originalFile: "/avs-originals/embedded/root/embedded/Dynamics/06 dynamic duo - tentaclism (midas remix).avs",
+ 678→ approx: true,
+ 679→ content: "name: \"tentaclism (midas remix)\"\nmode: dots\nblend: additive\npersist: 0.848\nlinewidth: 2.168\n\n[init]\nn = 757\n\n[frame]\ntt = tt + dt * (0.5 + bass_att * 1.1)\nhue = hue + dt * 0.053\nburst = burst * 0.9\n\n[beat]\nburst = 1\n\n[point]\njet = floor(i * 2)\nlife = frac(rand(i * 7.7 + jet) + tt * (0.35 + rand(i + 3) * 0.65))\na = jet * 6.28318 / 2 + rand(i * 5.1 + jet) * 1.14\nspd = 0.4 + rand(i * 9.2 + jet) * 0.8\npx = cos(a) * spd * life\npy = sin(a) * spd * life - 1.009 * life * life\nx = px * (0.85 + bass * 0.35) / aspect\ny = py\nred = min(1, max(0, 1 - life) * (0.35 + burst * 0.55) * 1.3)\ngreen = max(0, 1 - life) * (0.35 + burst * 0.55) * 0.5\nblue = max(0, 1 - life) * (0.35 + burst * 0.55) * 0.12\nskip = if(above(life, 0.94), 1, 0)\n",
+ 680→ },
+ 681→ {
+ 682→ folder: "root/embedded/Dynamics",
+ 683→ name: "tentaclism (raz remix - ugly edit)",
+ 684→ author: "dynamic duo",
+ 685→ description: "Auto-ported from the Winamp AVS preset \"07 dynamic duo - tentaclism (raz remix - ugly edit).avs\" by dynamic duo. The original is driven by unportable scope code (frame line 20: Expected ')' but found ';'), which the script engine cannot run -- replaced with a generated \"spectrum bars\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 686→ originalFile: "/avs-originals/embedded/root/embedded/Dynamics/07 dynamic duo - tentaclism (raz remix - ugly edit).avs",
+ 687→ approx: true,
+ 688→ content: "name: \"tentaclism (raz remix - ugly edit)\"\nmode: lines\nblend: additive\npersist: 0.873\nlinewidth: 1.756\n\n[init]\nbars = 44\nn = bars * 11\n\n[frame]\nrot = rot + dt * (0.118 + mid * 0.3)\nhue = hue + dt * 0.119\n\n[point]\nbar = floor(i * bars)\nbi = frac(i * bars)\nlvl = getspec(bar / bars, 0, 0)\nh = lvl * 0.767\na = bar * 6.28318 / bars + rot\nrad = 0.145 + bi * h\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = 0.25 + bi * 0.75 * (0.45 + 0.55 * sin(hue * 6.28318 + bar * 6.28318 / bars))\ngreen = 0.25 + bi * 0.75 * (0.45 + 0.55 * sin(hue * 6.28318 + bar * 6.28318 / bars + 2.09))\nblue = 0.25 + bi * 0.75 * (0.45 + 0.55 * sin(hue * 6.28318 + bar * 6.28318 / bars + 4.19))\nskip = if(above(bi, 0.97), 1, 0)\n",
+ 689→ },
+ 690→ {
+ 691→ folder: "root/embedded/Dynamics",
+ 692→ name: "tentaclism (the y.m.o. rework #1)",
+ 693→ author: "dynamic duo",
+ 694→ description: "Auto-ported from the Winamp AVS preset \"08 dynamic duo - tentaclism (the y.m.o. rework #1).avs\" by dynamic duo. The original is driven by unportable scope code (frame line 20: Expected ')' but found ';'), which the script engine cannot run -- replaced with a generated \"web\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 695→ originalFile: "/avs-originals/embedded/root/embedded/Dynamics/08 dynamic duo - tentaclism (the y.m.o. rework #1).avs",
+ 696→ approx: true,
+ 697→ content: "name: \"tentaclism (the y.m.o. rework #1)\"\nmode: dots\nblend: additive\npersist: 0.831\nlinewidth: 1.694\n\n[init]\nspokes = 32\nn = spokes * 27\n\n[frame]\nrot = rot + dt * (0.073 + mid * 0.4)\nt = t + dt * 1.807\nhue = hue + dt * 0.131\n\n[point]\nsp = floor(i * spokes)\nsi = frac(i * spokes)\na = sp * 6.28318 / spokes + rot\nwave = sin(si * 2 * 3.14159 - t) * (0.03 + bass * 0.28)\nrad = 0.12 + si * 0.758 + wave\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = (1 - si) * 0.45 + 0.15 + sv * 0.55 * abs(sin(sp * 0.4))\ngreen = (1 - si) * 0.45 + 0.15 + sv * 0.55 * 0.22\nblue = (1 - si) * 0.45 + 0.15 + sv * 0.55 * abs(cos(sp * 0.4))\nskip = if(above(si, 0.985), 1, 0)\n",
+ 698→ },
+ 699→ {
+ 700→ folder: "root/embedded/Dynamics",
+ 701→ name: "tentaclism (kans of kansis mix by drew - edit)",
+ 702→ author: "dynamic duo",
+ 703→ description: "Auto-ported from the Winamp AVS preset \"09 dynamic duo - tentaclism (kans of kansis mix by drew - edit).avs\" by dynamic duo. The original is driven by unportable scope code (frame line 20: Expected ')' but found ';'), which the script engine cannot run -- replaced with a generated \"moire\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 704→ originalFile: "/avs-originals/embedded/root/embedded/Dynamics/09 dynamic duo - tentaclism (kans of kansis mix by drew - edit).avs",
+ 705→ approx: true,
+ 706→ content: "name: \"tentaclism (kans of kansis mix by drew - edit)\"\nmode: dots\nblend: additive\npersist: 0.827\nlinewidth: 2.039\n\n[init]\nn = 1449\n\n[frame]\nt = t + dt * 0.447\nhue = hue + dt * 0.034\n\n[point]\nu = rand(i * 1.7) * 2 - 1\nw = rand(i * 3.3 + 11) * 2 - 1\nm = sin((u * 6.164 + w * 5.725 + t) * 3.14159) * sin((u * 2.761 - w * 2.167 - t * 0.7) * 3.14159)\nx = u * 0.936 / aspect\ny = w * 0.903\nlum = max(0, m) * (0.3 + bass * 0.9) + 0.06\nred = min(1, lum * 1.3)\ngreen = lum * 0.5\nblue = lum * 0.12\nsize = 1 + abs(m) * 3.26\n",
+ 707→ },
+ 708→ {
+ 709→ folder: "root/embedded/Envision",
+ 710→ name: "the pacifist (duo's underworld mix)",
+ 711→ author: "amphirion",
+ 712→ description: "Auto-ported from the Winamp AVS preset \"01 amphirion - the pacifist (duo's underworld mix).avs\" by amphirion. The original is driven by a trivial scope (n=4; the real work is movement/APE driven), which the script engine cannot run -- replaced with a generated \"kaleido\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 713→ originalFile: "/avs-originals/embedded/root/embedded/Envision/01 amphirion - the pacifist (duo's underworld mix).avs",
+ 714→ approx: true,
+ 715→ content: "name: \"the pacifist (duo's underworld mix)\"\nmode: lines\nblend: additive\npersist: 0.856\nlinewidth: 1.75\n\n[init]\nsegs = 14\nn = 483\n\n[frame]\nrot = rot + dt * (0.149 + mid_att * 0.5)\nhue = hue + dt * 0.117\nflash = flash * 0.85\n\n[beat]\nflash = 1\n\n[point]\nseg = floor(i * segs)\nsi = frac(i * segs)\na0 = si * 6.28318 / segs + rot\namp = 0.1 + sv * 0.55 + bass * 0.25 + flash * 0.06\nrad = 0.15 + amp * (0.4 + 0.6 * sin(a0 * 4 + time * 2))\na = seg * 6.28318 / segs + a0 * if(below(seg % 2, 1), 1, -1)\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = 0.3 + amp * 1.3 * (0.45 + 0.55 * sin(hue * 6.28318 + seg * 0.8))\ngreen = 0.3 + amp * 1.3 * (0.45 + 0.55 * sin(hue * 6.28318 + seg * 0.8 + 2.09))\nblue = 0.3 + amp * 1.3 * (0.45 + 0.55 * sin(hue * 6.28318 + seg * 0.8 + 4.19))\nskip = if(above(si, 0.96), 1, 0)\n",
+ 716→ },
+ 717→ {
+ 718→ folder: "root/embedded/Envision",
+ 719→ name: "biofeedback (hboy's organic mix)",
+ 720→ author: "duo",
+ 721→ description: "Auto-ported from the Winamp AVS preset \"02 duo - biofeedback (hboy's organic mix).avs\" by duo. Original superscope code, machine-translated to the AVA script dialect.",
+ 722→ originalFile: "/avs-originals/embedded/root/embedded/Envision/02 duo - biofeedback (hboy's organic mix).avs",
+ 723→ approx: false,
+ 724→ content: "# Auto-ported from \"02 duo - biofeedback (hboy's organic mix).avs\" (Winamp AVS / duo)\n# SuperScope point program, 4 AVSTrans macros expanded.\nname: \"biofeedback (hboy's organic mix)\"\nauthor: \"duo\"\nmode: lines\nblend: additive\npersist: 0.88\nlinewidth: 2\nysign: -1\n\n[init]\nn=50\n\n[frame]\nbg=sin(bt);\r\nfr=fr+bt-(fr*.055);\r\npf=pf+.025;\n\n[beat]\nbt=getspec(0,0,0)*.55;\n\n[point]\nc=i*(3.14159265358979)*2;\r\nx=cos(c);\r\ny=c*sin(fr*.3)*cos(pf)*.1;\r\nsize=20+fr+v*20;\r\n\r\ncol=cos(i*19);\r\nred=col;\r\ngreen=col;\r\nblue=col;\n",
+ 725→ },
+ 726→ {
+ 727→ folder: "root/embedded/Envision",
+ 728→ name: "roll-on mojo (duo remix)",
+ 729→ author: "zamuz",
+ 730→ description: "Auto-ported from the Winamp AVS preset \"04 zamuz - roll-on mojo (duo remix).avs\" by zamuz. The original is driven by a trivial scope (the real work is movement/APE driven), which the script engine cannot run -- replaced with a generated \"swirl\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 731→ originalFile: "/avs-originals/embedded/root/embedded/Envision/04 zamuz - roll-on mojo (duo remix).avs",
+ 732→ approx: true,
+ 733→ content: "name: \"roll-on mojo (duo remix)\"\nmode: lines\nblend: additive\npersist: 0.889\nlinewidth: 1.751\n\n[init]\nn = 950\n\n[frame]\nt = t + dt * (0.251 + bass_att * 1.8)\nhue = hue + dt * 0.089 + beat * 0.012\n\n[point]\narm = floor(i * 3)\nfa = frac(i * 3)\nd = fa\nang = arm * 1.538 + d * 1.277 * 3.14159 + t\nrad = (0.04 + pow(d, 1.775) * 1.15) * (0.7 + bass * 0.5)\nx = sin(ang) * rad / aspect\ny = cos(ang) * rad\nred = (0.3 + 0.7 * d) * (0.3 + mid) * abs(sin(d * 3.995))\ngreen = (0.3 + 0.7 * d) * (0.3 + mid) * 0.22\nblue = (0.3 + 0.7 * d) * (0.3 + mid) * abs(cos(d * 3.995))\nskip = if(below(fa, 0.04), 1, 0)\n",
+ 734→ },
+ 735→ {
+ 736→ folder: "root/embedded/Envision",
+ 737→ name: "cafe de duo (vanish's wicked jungle massive remix)",
+ 738→ author: "duo",
+ 739→ description: "Auto-ported from the Winamp AVS preset \"05 duo - cafe de duo (vanish's wicked jungle massive remix).avs\" by duo. The original is driven by unportable scope code (frame line 19: Expected ')' but found ';'), which the script engine cannot run -- replaced with a generated \"kaleido\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 740→ originalFile: "/avs-originals/embedded/root/embedded/Envision/05 duo - cafe de duo (vanish's wicked jungle massive remix).avs",
+ 741→ approx: true,
+ 742→ content: "name: \"cafe de duo (vanish's wicked jungle massive remix)\"\nmode: lines\nblend: additive\npersist: 0.873\nlinewidth: 1.915\n\n[init]\nsegs = 10\nn = 623\n\n[frame]\nrot = rot + dt * (0.112 + mid_att * 0.5)\nhue = hue + dt * 0.038\nflash = flash * 0.85\n\n[beat]\nflash = 1\n\n[point]\nseg = floor(i * segs)\nsi = frac(i * segs)\na0 = si * 6.28318 / segs + rot\namp = 0.1 + sv * 0.55 + bass * 0.25 + flash * 0.06\nrad = 0.15 + amp * (0.4 + 0.6 * sin(a0 * 9 + time * 2))\na = seg * 6.28318 / segs + a0 * if(below(seg % 2, 1), 1, -1)\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = 0.3 + amp * 1.3 * (0.25 + bass * 0.95)\ngreen = 0.3 + amp * 1.3 * (0.25 + mid * 0.95)\nblue = 0.3 + amp * 1.3 * (0.25 + treb * 0.95)\nskip = if(above(si, 0.96), 1, 0)\n",
+ 743→ },
+ 744→ {
+ 745→ folder: "root/embedded/Envision",
+ 746→ name: "incantation (duo remix - edit)",
+ 747→ author: "skupers",
+ 748→ description: "Auto-ported from the Winamp AVS preset \"06 skupers - incantation (duo remix - edit).avs\" by skupers. The original is driven by a trivial scope (n=4; the real work is movement/APE driven), which the script engine cannot run -- replaced with a generated \"kaleido\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 749→ originalFile: "/avs-originals/embedded/root/embedded/Envision/06 skupers - incantation (duo remix - edit).avs",
+ 750→ approx: true,
+ 751→ content: "name: \"incantation (duo remix - edit)\"\nmode: lines\nblend: additive\npersist: 0.892\nlinewidth: 1.682\n\n[init]\nsegs = 14\nn = 476\n\n[frame]\nrot = rot + dt * (0.088 + mid_att * 0.5)\nhue = hue + dt * 0.074\nflash = flash * 0.85\n\n[beat]\nflash = 1\n\n[point]\nseg = floor(i * segs)\nsi = frac(i * segs)\na0 = si * 6.28318 / segs + rot\namp = 0.1 + sv * 0.55 + bass * 0.25 + flash * 0.06\nrad = 0.15 + amp * (0.4 + 0.6 * sin(a0 * 6 + time * 2))\na = seg * 6.28318 / segs + a0 * if(below(seg % 2, 1), 1, -1)\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = 0.3 + amp * 1.3 * abs(sin(seg * 0.8))\ngreen = 0.3 + amp * 1.3 * 0.22\nblue = 0.3 + amp * 1.3 * abs(cos(seg * 0.8))\nskip = if(above(si, 0.96), 1, 0)\n",
+ 752→ },
+ 753→ {
+ 754→ folder: "root/embedded/Envision",
+ 755→ name: "miraculous flow (nemo remix)",
+ 756→ author: "duo",
+ 757→ description: "Auto-ported from the Winamp AVS preset \"07 duo - miraculous flow (nemo remix).avs\" by duo. The original is driven by UniqueTone, DynamicMovement, FastBright, which the script engine cannot run -- replaced with a generated \"ribbon\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 758→ originalFile: "/avs-originals/embedded/root/embedded/Envision/07 duo - miraculous flow (nemo remix).avs",
+ 759→ approx: true,
+ 760→ content: "name: \"miraculous flow (nemo remix)\"\nmode: lines\nblend: additive\npersist: 0.91\nlinewidth: 1.628\n\n[init]\nn = 981\n\n[frame]\nt = t + dt * (0.579 + bass_att * 0.8)\nsway = sway + dt * (0.5 + treb)\nhue = hue + dt * 0.122\n\n[point]\na = i * 6.28318 * 3\nwob = sin(a * 6 + t * 2) * (0.08 + treb * 0.3)\nrad = (0.42 + mid * 0.25) * (0.85 + 0.15 * sin(a * 3 + sway)) + v * 0.202 - wob\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = min(1, 0.3 + abs(v) * 1.2 * 1.3)\ngreen = 0.3 + abs(v) * 1.2 * 0.5\nblue = 0.3 + abs(v) * 1.2 * 0.12\n",
+ 761→ },
+ 762→ {
+ 763→ folder: "root/embedded/Envision",
+ 764→ name: "warm freeform (mykal remix)",
+ 765→ author: "duo",
+ 766→ description: "Auto-ported from the Winamp AVS preset \"08 duo - warm freeform (mykal remix).avs\" by duo. The original is driven by a trivial scope (the real work is movement/APE driven), which the script engine cannot run -- replaced with a generated \"web\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 767→ originalFile: "/avs-originals/embedded/root/embedded/Envision/08 duo - warm freeform (mykal remix).avs",
+ 768→ approx: true,
+ 769→ content: "name: \"warm freeform (mykal remix)\"\nmode: dots\nblend: additive\npersist: 0.898\nlinewidth: 1.303\n\n[init]\nspokes = 32\nn = spokes * 37\n\n[frame]\nrot = rot + dt * (0.135 + mid * 0.4)\nt = t + dt * 2.906\nhue = hue + dt * 0.077\n\n[point]\nsp = floor(i * spokes)\nsi = frac(i * spokes)\na = sp * 6.28318 / spokes + rot\nwave = sin(si * 3 * 3.14159 - t) * (0.03 + bass * 0.28)\nrad = 0.12 + si * 0.868 + wave\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = (1 - si) * 0.45 + 0.15 + sv * 0.55 * abs(sin(sp * 0.4))\ngreen = (1 - si) * 0.45 + 0.15 + sv * 0.55 * 0.22\nblue = (1 - si) * 0.45 + 0.15 + sv * 0.55 * abs(cos(sp * 0.4))\nskip = if(above(si, 0.985), 1, 0)\n",
+ 770→ },
+ 771→ {
+ 772→ folder: "root/embedded/Envision",
+ 773→ name: "pleasure point (duo remix)",
+ 774→ author: "zxe",
+ 775→ description: "Auto-ported from the Winamp AVS preset \"09 zxe - pleasure point (duo remix).avs\" by zxe. The original is driven by a trivial scope (the real work is movement/APE driven), which the script engine cannot run -- replaced with a generated \"lissajous\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 776→ originalFile: "/avs-originals/embedded/root/embedded/Envision/09 zxe - pleasure point (duo remix).avs",
+ 777→ approx: true,
+ 778→ content: "name: \"pleasure point (duo remix)\"\nmode: lines\nblend: additive\npersist: 0.87\nlinewidth: 1.808\n\n[init]\nn = 856\n\n[frame]\nt = t + dt * 0.572\nhue = hue + dt * 0.036\n\n[point]\nph = i * 6.28318 * 2.087\nfa = 2 + mid * 0.35\nfb = 7 + bass * 0.45\nx = sin(ph * fa + t) * (0.5 + bass * 0.3) / aspect\ny = sin(ph * fb + t * 1.3 + 1.499) * (0.5 + treb * 0.35)\nred = 0.3 + 0.45 * sin(ph * 2 + t) + abs(v) * 0.6 * 0.3\ngreen = 0.3 + 0.45 * sin(ph * 2 + t) + abs(v) * 0.6 * 0.65\nblue = min(1, 0.3 + 0.45 * sin(ph * 2 + t) + abs(v) * 0.6 * 1.3)\n",
+ 779→ },
+ 780→ {
+ 781→ folder: "root/embedded/Envision",
+ 782→ name: "heart dynamics (duo's high dose mix)",
+ 783→ author: "zamuz",
+ 784→ description: "Auto-ported from the Winamp AVS preset \"10 zamuz - heart dynamics (duo's high dose mix).avs\" by zamuz. The original is driven by a trivial scope (the real work is movement/APE driven), which the script engine cannot run -- replaced with a generated \"ribbon\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 785→ originalFile: "/avs-originals/embedded/root/embedded/Envision/10 zamuz - heart dynamics (duo's high dose mix).avs",
+ 786→ approx: true,
+ 787→ content: "name: \"heart dynamics (duo's high dose mix)\"\nmode: lines\nblend: additive\npersist: 0.909\nlinewidth: 1.819\n\n[init]\nn = 1105\n\n[frame]\nt = t + dt * (0.35 + bass_att * 0.8)\nsway = sway + dt * (0.5 + treb)\nhue = hue + dt * 0.091\n\n[point]\na = i * 6.28318 * 3\nwob = sin(a * 5 + t * 2) * (0.08 + treb * 0.3)\nrad = (0.42 + mid * 0.25) * (0.85 + 0.15 * sin(a * 3 + sway)) + v * 0.184 - wob\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = 0.3 + abs(v) * 1.2 * abs(sin(a * 0.5))\ngreen = 0.3 + abs(v) * 1.2 * 0.22\nblue = 0.3 + abs(v) * 1.2 * abs(cos(a * 0.5))\n",
+ 788→ },
+ 789→ {
+ 790→ folder: "root/embedded/Envision",
+ 791→ name: "we build here (duo's building blocks remix)",
+ 792→ author: "nemo",
+ 793→ description: "Auto-ported from the Winamp AVS preset \"11 nemo - we build here (duo's building blocks remix).avs\" by nemo. The original is driven by DynamicMovement, BufferSave, 2.8+ Effect List Config, which the script engine cannot run -- replaced with a generated \"moire\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 794→ originalFile: "/avs-originals/embedded/root/embedded/Envision/11 nemo - we build here (duo's building blocks remix).avs",
+ 795→ approx: true,
+ 796→ content: "name: \"we build here (duo's building blocks remix)\"\nmode: dots\nblend: additive\npersist: 0.739\nlinewidth: 2.311\n\n[init]\nn = 1183\n\n[frame]\nt = t + dt * 0.515\nhue = hue + dt * 0.134\n\n[point]\nu = rand(i * 1.7) * 2 - 1\nw = rand(i * 3.3 + 11) * 2 - 1\nm = sin((u * 6.135 + w * 2.876 + t) * 3.14159) * sin((u * 3.312 - w * 5.365 - t * 0.7) * 3.14159)\nx = u * 0.99 / aspect\ny = w * 0.98\nlum = max(0, m) * (0.3 + bass * 0.9) + 0.06\nred = min(1, lum * 1.3)\ngreen = lum * 0.5\nblue = lum * 0.12\nsize = 1 + abs(m) * 3.466\n",
+ 797→ },
+ 798→ {
+ 799→ folder: "root/embedded/Envision",
+ 800→ name: "wickedjunglesmassive (yathosho's cartographic re-rub)",
+ 801→ author: "duo & vanish",
+ 802→ description: "Auto-ported from the Winamp AVS preset \"12 duo & vanish - wickedjunglesmassive (yathosho's cartographic re-rub).avs\" by duo & vanish. The original is driven by unportable scope code (frame line 19: Expected ')' but found ';'), which the script engine cannot run -- replaced with a generated \"moire\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 803→ originalFile: "/avs-originals/embedded/root/embedded/Envision/12 duo & vanish - wickedjunglesmassive (yathosho's cartographic re-rub).avs",
+ 804→ approx: true,
+ 805→ content: "name: \"wickedjunglesmassive (yathosho's cartographic re-rub)\"\nmode: dots\nblend: additive\npersist: 0.805\nlinewidth: 1.835\n\n[init]\nn = 1292\n\n[frame]\nt = t + dt * 0.675\nhue = hue + dt * 0.035\n\n[point]\nu = rand(i * 1.7) * 2 - 1\nw = rand(i * 3.3 + 11) * 2 - 1\nm = sin((u * 3.6 + w * 6.394 + t) * 3.14159) * sin((u * 5.053 - w * 4.781 - t * 0.7) * 3.14159)\nx = u * 0.897 / aspect\ny = w * 0.959\nlum = max(0, m) * (0.3 + bass * 0.9) + 0.06\nred = lum * (0.45 + 0.55 * sin(hue * 6.28318 + u * 4.308))\ngreen = lum * (0.45 + 0.55 * sin(hue * 6.28318 + u * 4.308 + 2.09))\nblue = lum * (0.45 + 0.55 * sin(hue * 6.28318 + u * 4.308 + 4.19))\nsize = 1 + abs(m) * 2.706\n",
+ 806→ },
+ 807→ {
+ 808→ folder: "root/embedded/Envision",
+ 809→ name: "why i love you (duo's pulsedriver remix)",
+ 810→ author: "skupers",
+ 811→ description: "Auto-ported from the Winamp AVS preset \"13 skupers - why i love you (duo's pulsedriver remix).avs\" by skupers. The original is driven by a trivial scope (n=10; the real work is movement/APE driven), which the script engine cannot run -- replaced with a generated \"kaleido\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 812→ originalFile: "/avs-originals/embedded/root/embedded/Envision/13 skupers - why i love you (duo's pulsedriver remix).avs",
+ 813→ approx: true,
+ 814→ content: "name: \"why i love you (duo's pulsedriver remix)\"\nmode: lines\nblend: additive\npersist: 0.843\nlinewidth: 1.547\n\n[init]\nsegs = 8\nn = 528\n\n[frame]\nrot = rot + dt * (0.074 + mid_att * 0.5)\nhue = hue + dt * 0.052\nflash = flash * 0.85\n\n[beat]\nflash = 1\n\n[point]\nseg = floor(i * segs)\nsi = frac(i * segs)\na0 = si * 6.28318 / segs + rot\namp = 0.1 + sv * 0.55 + bass * 0.25 + flash * 0.06\nrad = 0.15 + amp * (0.4 + 0.6 * sin(a0 * 6 + time * 2))\na = seg * 6.28318 / segs + a0 * if(below(seg % 2, 1), 1, -1)\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nred = 0.3 + amp * 1.3 * 0.85\ngreen = 0.3 + amp * 1.3 * 0.92\nblue = 0.3 + amp * 1.3\nskip = if(above(si, 0.96), 1, 0)\n",
+ 815→ },
+ 816→ {
+ 817→ folder: "root/embedded/Envision",
+ 818→ name: "conjuring sands (skupers remix - edit)",
+ 819→ author: "duo",
+ 820→ description: "Auto-ported from the Winamp AVS preset \"14 duo - conjuring sands (skupers remix - edit).avs\" by duo. The original is driven by a trivial scope (n=15; the real work is movement/APE driven), which the script engine cannot run -- replaced with a generated \"lissajous\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 821→ originalFile: "/avs-originals/embedded/root/embedded/Envision/14 duo - conjuring sands (skupers remix - edit).avs",
+ 822→ approx: true,
+ 823→ content: "name: \"conjuring sands (skupers remix - edit)\"\nmode: lines\nblend: additive\npersist: 0.922\nlinewidth: 2.216\n\n[init]\nn = 874\n\n[frame]\nt = t + dt * 0.211\nhue = hue + dt * 0.039\n\n[point]\nph = i * 6.28318 * 2.729\nfa = 6 + mid * 0.35\nfb = 5 + bass * 0.45\nx = sin(ph * fa + t) * (0.5 + bass * 0.3) / aspect\ny = sin(ph * fb + t * 1.3 + 1.092) * (0.5 + treb * 0.35)\nred = 0.3 + 0.45 * sin(ph * 2 + t) + abs(v) * 0.6 * abs(sin(ph * 0.5))\ngreen = 0.3 + 0.45 * sin(ph * 2 + t) + abs(v) * 0.6 * 0.22\nblue = 0.3 + 0.45 * sin(ph * 2 + t) + abs(v) * 0.6 * abs(cos(ph * 0.5))\n",
+ 824→ },
+ 825→ {
+ 826→ folder: "root/embedded/Envision",
+ 827→ name: "multiple overdose (duo remix)",
+ 828→ author: "tuggummi",
+ 829→ description: "Auto-ported from the Winamp AVS preset \"15 tuggummi - multiple overdose (duo remix).avs\" by tuggummi. Original superscope code, machine-translated to the AVA script dialect.",
+ 830→ originalFile: "/avs-originals/embedded/root/embedded/Envision/15 tuggummi - multiple overdose (duo remix).avs",
+ 831→ approx: false,
+ 832→ content: "# Auto-ported from \"15 tuggummi - multiple overdose (duo remix).avs\" (Winamp AVS / tuggummi)\n# SuperScope point program, 4 AVSTrans macros expanded.\nname: \"multiple overdose (duo remix)\"\nauthor: \"tuggummi\"\nmode: lines\nblend: additive\npersist: 0.88\nlinewidth: 2\nysign: -1\n\n[init]\npi=acos(-1) ; t=acos(-1)\n\n[frame]\nn=w*0.55+2 ; t=t+0.25\n\n[beat]\nflash = 1\n\n[point]\ns=bnot(s) ;\r\nx=i*2-1 ;\r\ny=s*0.25-0.225-v*.2 ;\r\nc=sin(i*pi*2+t)*0.5+0.5 ;\r\nred=c ;\r\nblue=c ;\r\ngreen=c;\r\nsize=3;\n",
+ 833→ },
+ 834→ {
+ 835→ folder: "root/embedded/Envision",
+ 836→ name: "warm freeform (ya-tho-sho treatment)",
+ 837→ author: "duo",
+ 838→ description: "Auto-ported from the Winamp AVS preset \"16 duo - warm freeform (ya-tho-sho treatment).avs\" by duo. The original is driven by a trivial scope (the real work is movement/APE driven), which the script engine cannot run -- replaced with a generated \"pulse rings\"-style visual, uniquely parameterized for this preset (beat & band reactive).",
+ 839→ originalFile: "/avs-originals/embedded/root/embedded/Envision/16 duo - warm freeform (ya-tho-sho treatment).avs",
+ 840→ approx: true,
+ 841→ content: "name: \"warm freeform (ya-tho-sho treatment)\"\nmode: lines\nblend: additive\npersist: 0.855\nlinewidth: 1.459\n\n[init]\nn = 572\n\n[frame]\nhue = hue + dt * 0.129 + beat * 0.01\ntravel = travel + dt * (0.44 + bass_att * 1.4)\nflash = flash * 0.86\n\n[beat]\nflash = 1\n\n[point]\ng = floor(i * 13)\ngi = frac(i * 13)\nph = frac(rand(g * 5.1 + 1) + travel * (0.55 + rand(g + 17) * 0.85))\na = gi * 6.28318\nrad = ph * (0.305 + bass * 0.472 + flash * 0.08)\nx = sin(a) * rad / aspect\ny = cos(a) * rad\nfade = sin(ph * 3.14159)\nred = fade * (0.35 + flash * 0.4) * 0.85\ngreen = fade * (0.35 + flash * 0.4) * 0.92\nblue = fade * (0.35 + flash * 0.4)\nskip = if(above(ph, 0.96), 1, 0)\n",
+ 842→ },
+ 843→
+ 844→ // ==========================================================================
+ 845→ // Zai-Magic — ten presets hand-authored for this engine (v7.00).
+ 846→ // Every one is built around BEAT-SYNCED COLOR: the color wheel turns on
+ 847→ // the kick, palettes swap per beat or per 4-beat bar, and kick envelopes
+ 848→ // (beatage) drive brightness. Techniques span all three render modes,
+ 849→ // megabuf particle physics, direct getspec() calls and loop() setup.
+ 850→ // KEEP VERBATIM when regenerating this file with the converter.
+ 851→ // ==========================================================================
+ 852→ {
+ 853→ folder: "root/embedded/Zai-Magic",
+ 854→ name: "Prism Chandelier",
+ 855→ author: "Zai",
+ 856→ description:
+ 857→ "Six mirrored ribbons spin as one kaleidoscope; every beat turns the color wheel a notch and the kick flares the whole prism. Hand-authored for OVE -- hsv rainbow + beatage kick envelope.",
+ 858→ content: `name: "Prism Chandelier"
+ 859→author: "Zai"
+ 860→mode: lines
+ 861→blend: additive
+ 862→persist: 0.87
+ 863→linewidth: 1.6
+ 864→
+ 865→[init]
+ 866→mirrors = 6
+ 867→n = 1140
+ 868→chroma = 0.13
+ 869→rot = 0
+ 870→
+ 871→[frame]
+ 872→t = t + dt
+ 873→rot = rot + dt * (0.25 + mid_att * 0.6)
+ 874→chroma = frac(chroma + dt * 0.045 + beat * 0.085)
+ 875→pulse = exp(-beatage * 3.2)
+ 876→glow = 0.35 + bass_att * 0.45 + pulse * 0.35
+ 877→
+ 878→[point]
+ 879→seg = floor(i * mirrors)
+ 880→si = frac(i * mirrors)
+ 881→ph = si * pi
+ 882→bx = sin(ph * 2 + t * 1.35) * (0.34 + bass * 0.18)
+ 883→by = cos(ph * 3 + t * 0.9) * (0.26 + mid * 0.16)
+ 884→ca = cos(rot + seg * tau / mirrors)
+ 885→sa = sin(rot + seg * tau / mirrors)
+ 886→x = (bx * ca - by * sa) / aspect
+ 887→y = bx * sa + by * ca
+ 888→hue = chroma + seg / mirrors + si * 0.08
+ 889→red = hsvr(hue, 1, glow)
+ 890→green = hsvg(hue, 1, glow)
+ 891→blue = hsvb(hue, 1, glow)
+ 892→skip = if(below(si, 0.015), 1, 0)
+ 893→`,
+ 894→ },
+ 895→ {
+ 896→ folder: "root/embedded/Zai-Magic",
+ 897→ name: "Neon Heartbeat",
+ 898→ author: "Zai",
+ 899→ description:
+ 900→ "A solid-mode bloom that throbs with the bass -- the fill hue jumps on every beat and the petals breathe on the kick envelope. Hand-authored for OVE (solid mode + hsv color).",
+ 901→ content: `name: "Neon Heartbeat"
+ 902→author: "Zai"
+ 903→mode: solid
+ 904→blend: additive
+ 905→persist: 0.82
+ 906→linewidth: 2
+ 907→
+ 908→[init]
+ 909→n = 240
+ 910→hue = 0.55
+ 911→spin = 0
+ 912→
+ 913→[frame]
+ 914→t = t + dt
+ 915→spin = spin + dt * 0.4
+ 916→hue = frac(hue + beat * 0.22)
+ 917→pulse = exp(-beatage * 4.5)
+ 918→petals = 5 + pulse * 1.5
+ 919→
+ 920→[point]
+ 921→li = if(below(i, 0.5), i * 2, (i - 0.5) * 2)
+ 922→back = if(below(i, 0.5), 1, -1)
+ 923→ang = li * tau + spin * back
+ 924→petal = 1 + 0.42 * sin(petals * ang + t * 1.7)
+ 925→rad = if(below(i, 0.5), 0.62 + bass * 0.16 + pulse * 0.2, 0.3 + treb * 0.1 + pulse * 0.12) * petal
+ 926→x = sin(ang) * rad / aspect
+ 927→y = cos(ang) * rad
+ 928→red = hsvr(hue, 0.9, 0.42 + pulse * 0.5)
+ 929→green = hsvg(hue, 0.9, 0.42 + pulse * 0.5)
+ 930→blue = hsvb(hue, 0.9, 0.42 + pulse * 0.5)
+ 931→skip = if(below(li, 0.008), 1, 0)
+ 932→`,
+ 933→ },
+ 934→ {
+ 935→ folder: "root/embedded/Zai-Magic",
+ 936→ name: "Spectrum Serpent",
+ 937→ author: "Zai",
+ 938→ description:
+ 939→ "An oscilloscope serpent coiled into a spiral -- the body swells where the spectrum is loud and the rainbow shifts a step on every beat. Hand-authored for OVE.",
+ 940→ content: `name: "Spectrum Serpent"
+ 941→author: "Zai"
+ 942→mode: lines
+ 943→blend: additive
+ 944→persist: 0.9
+ 945→linewidth: 2.2
+ 946→
+ 947→[init]
+ 948→n = 900
+ 949→chroma = 0
+ 950→turns = 4.5
+ 951→
+ 952→[frame]
+ 953→t = t + dt * (0.7 + bass_att * 0.9)
+ 954→chroma = frac(chroma + dt * 0.06 + beat * 0.14)
+ 955→pulse = exp(-beatage * 3.5)
+ 956→
+ 957→[point]
+ 958→a = i * tau * turns + t
+ 959→r = 0.07 + pow(i, 0.85) * 0.82 * (0.55 + bass_att * 0.55 + pulse * 0.12)
+ 960→wig = sin(a * 3 + t * 2.1) * 0.09 * (0.4 + mid)
+ 961→x = (sin(a) * r + wig * cos(a)) / aspect
+ 962→y = cos(a) * r + wig * sin(a)
+ 963→hue = chroma + i * 0.45
+ 964→lum = 0.3 + sv * 1.1 * (0.6 + vol_att * 0.8) + pulse * 0.18
+ 965→red = hsvr(hue, 0.95, min(1, lum))
+ 966→green = hsvg(hue, 0.95, min(1, lum))
+ 967→blue = hsvb(hue, 0.95, min(1, lum))
+ 968→size = 1.2 + sv * 4
+ 969→alpha = 0.45 + 0.55 * sin(i * pi)
+ 970→`,
+ 971→ },
+ 972→ {
+ 973→ folder: "root/embedded/Zai-Magic",
+ 974→ name: "Bass Bloom",
+ 975→ author: "Zai",
+ 976→ description:
+ 977→ "A fountain of 480 megabuf particles launched by every beat -- each petal carries the exact hue of the beat that spawned it, so the falling arcs leave a rainbow memory of the music. Hand-authored for OVE.",
+ 978→ content: `name: "Bass Bloom"
+ 979→author: "Zai"
+ 980→mode: dots
+ 981→blend: additive
+ 982→persist: 0.9
+ 983→linewidth: 2
+ 984→
+ 985→[init]
+ 986→n = 480
+ 987→budget = 80
+ 988→chroma = 0.62
+ 989→k = 0
+ 990→loop(n, megabuf(k * 6 + 4) = -1; k = k + 1)
+ 991→
+ 992→[frame]
+ 993→t = t + dt
+ 994→budget = budget + 2 + beat * 340
+ 995→chroma = frac(chroma + beat * 0.13)
+ 996→
+ 997→[point]
+ 998→k = floor(i * n)
+ 999→b0 = k * 6
+ 1000→age = megabuf(b0 + 4)
+ 1001→if(band(below(age, 0), above(budget, 0)),
+ 1002→ (megabuf(b0 + 4) = 0.0001;
+ 1003→ megabuf(b0 + 5) = chroma;
+ 1004→ angk = rand(k * 3.13 + 0.3) * tau;
+ 1005→ spdk = 0.5 + rand(k * 7.77 + 3) * 0.9 + bass * 0.35;
+ 1006→ megabuf(b0) = 0;
+ 1007→ megabuf(b0 + 1) = 0;
+ 1008→ megabuf(b0 + 2) = cos(angk) * spdk;
+ 1009→ megabuf(b0 + 3) = sin(angk) * spdk;
+ 1010→ budget = budget - 1),
+ 1011→ 0)
+ 1012→age = megabuf(b0 + 4)
+ 1013→if(above(age, 0),
+ 1014→ (vy = megabuf(b0 + 3) - dt * 0.5;
+ 1015→ megabuf(b0 + 3) = vy;
+ 1016→ megabuf(b0) = megabuf(b0) + megabuf(b0 + 2) * dt;
+ 1017→ megabuf(b0 + 1) = megabuf(b0 + 1) + vy * dt;
+ 1018→ megabuf(b0 + 4) = age + dt;
+ 1019→ px = megabuf(b0);
+ 1020→ py = megabuf(b0 + 1);
+ 1021→ fade = 1 - age * 0.8;
+ 1022→ if(below(fade, 0.03), megabuf(b0 + 4) = -1, 0);
+ 1023→ if(above(abs(px) + abs(py), 2.3), megabuf(b0 + 4) = -1, 0);
+ 1024→ hue = megabuf(b0 + 5);
+ 1025→ x = px / aspect;
+ 1026→ y = py;
+ 1027→ red = hsvr(hue, 0.9, max(0, fade));
+ 1028→ green = hsvg(hue, 0.9, max(0, fade));
+ 1029→ blue = hsvb(hue, 0.9, max(0, fade));
+ 1030→ size = 1.1 + fade * 2.6;
+ 1031→ alpha = min(1, fade * 1.5)),
+ 1032→ (skip = 1))
+ 1033→`,
+ 1034→ },
+ 1035→ {
+ 1036→ folder: "root/embedded/Zai-Magic",
+ 1037→ name: "Strobe Lattice",
+ 1038→ author: "Zai",
+ 1039→ description:
+ 1040→ "Nested squares snap around and swap palette on every beat like a club strobe light; the flash decays on the beatage envelope while the lattice breathes with the bass. Hand-authored for OVE.",
+ 1041→ content: `name: "Strobe Lattice"
+ 1042→author: "Zai"
+ 1043→mode: lines
+ 1044→blend: additive
+ 1045→persist: 0.86
+ 1046→linewidth: 1.8
+ 1047→
+ 1048→[init]
+ 1049→rings = 5
+ 1050→sides = 4
+ 1051→per = 6
+ 1052→n = rings * per
+ 1053→rot = 0.2
+ 1054→chroma = 0
+ 1055→
+ 1056→[frame]
+ 1057→t = t + dt
+ 1058→rot = rot + dt * 0.22
+ 1059→flash = exp(-beatage * 6)
+ 1060→
+ 1061→[beat]
+ 1062→rot = rot + 0.62
+ 1063→chroma = frac(beats * 0.19)
+ 1064→
+ 1065→[point]
+ 1066→ring = floor(i * rings)
+ 1067→ri = frac(i * rings)
+ 1068→vert = floor(ri * per)
+ 1069→av = vert * tau / sides
+ 1070→fx = cos(av)
+ 1071→fy = sin(av)
+ 1072→scale = 0.2 + ring * 0.17 + bass_att * 0.08 + flash * 0.05
+ 1073→a = rot * (1 + ring * 0.13)
+ 1074→ca = cos(a)
+ 1075→sa = sin(a)
+ 1076→x = (fx * ca - fy * sa) * scale / aspect
+ 1077→y = (fx * sa + fy * ca) * scale
+ 1078→hue = chroma + ring * 0.055
+ 1079→lum = 0.32 + flash * 0.5 + mid_att * 0.28
+ 1080→red = hsvr(hue, 0.85, lum)
+ 1081→green = hsvg(hue, 0.85, lum)
+ 1082→blue = hsvb(hue, 0.85, lum)
+ 1083→skip = if(above(vert, sides + 0.5), 1, 0)
+ 1084→`,
+ 1085→ },
+ 1086→ {
+ 1087→ folder: "root/embedded/Zai-Magic",
+ 1088→ name: "Aurora Veil",
+ 1089→ author: "Zai",
+ 1090→ description:
+ 1091→ "Nine waving curtains, each lit by its own frequency band (getspec) -- the whole color scheme drifts once per 4-beat bar for slow, breathing color changes. Hand-authored for OVE.",
+ 1092→ content: `name: "Aurora Veil"
+ 1093→author: "Zai"
+ 1094→mode: lines
+ 1095→blend: additive
+ 1096→persist: 0.93
+ 1097→linewidth: 2.4
+ 1098→
+ 1099→[init]
+ 1100→curtains = 9
+ 1101→n = 1080
+ 1102→scheme = 0.33
+ 1103→
+ 1104→[frame]
+ 1105→t = t + dt
+ 1106→shimmer = sin(beatphase * tau)
+ 1107→
+ 1108→[beat]
+ 1109→if(equal(beats % 4, 0), scheme = frac(scheme + 0.17), 0)
+ 1110→
+ 1111→[point]
+ 1112→cu = floor(i * curtains)
+ 1113→ci = frac(i * curtains)
+ 1114→cx = (cu / (curtains - 1) - 0.5) * 1.9
+ 1115→band = getspec(cu / curtains, 0, 0)
+ 1116→x = (cx + sin(ci * 3.1 + t * 0.8 + cu * 0.7) * 0.16 * (0.25 + mid_att) + shimmer * 0.03) / aspect
+ 1117→y = ci * 1.9 - 0.95 + cos(ci * 4.2 - t * 1.1 + cu) * 0.05
+ 1118→hue = scheme + cu / curtains * 0.5 + ci * 0.06
+ 1119→lum = (0.2 + band * 1.35 * (0.5 + vol_att)) * (0.55 + 0.45 * sin(ci * pi))
+ 1120→red = hsvr(hue, 0.8, min(1, lum))
+ 1121→green = hsvg(hue, 0.8, min(1, lum))
+ 1122→blue = hsvb(hue, 0.72, min(1, lum * 1.18))
+ 1123→alpha = 0.5 + 0.5 * sin(ci * pi)
+ 1124→skip = if(below(ci, 0.01), 1, 0)
+ 1125→`,
+ 1126→ },
+ 1127→ {
+ 1128→ folder: "root/embedded/Zai-Magic",
+ 1129→ name: "Supernova",
+ 1130→ author: "Zai",
+ 1131→ description:
+ 1132→ "Every beat detonates the entire 620-particle field -- shells leave the core white-hot and saturate into the beat's hue as they expand and cool. Hand-authored for OVE (megabuf physics + drag).",
+ 1133→ content: `name: "Supernova"
+ 1134→author: "Zai"
+ 1135→mode: dots
+ 1136→blend: additive
+ 1137→persist: 0.88
+ 1138→linewidth: 2
+ 1139→
+ 1140→[init]
+ 1141→n = 620
+ 1142→budget = 620
+ 1143→chroma = 0.08
+ 1144→k = 0
+ 1145→loop(n, megabuf(k * 6 + 4) = -1; k = k + 1)
+ 1146→
+ 1147→[frame]
+ 1148→t = t + dt
+ 1149→budget = budget + 1
+ 1150→chroma = frac(chroma + beat * 0.21)
+ 1151→
+ 1152→[beat]
+ 1153→budget = n
+ 1154→k = 0
+ 1155→loop(n, megabuf(k * 6 + 4) = -1; k = k + 1)
+ 1156→
+ 1157→[point]
+ 1158→k = floor(i * n)
+ 1159→b0 = k * 6
+ 1160→age = megabuf(b0 + 4)
+ 1161→if(band(below(age, 0), above(budget, 0)),
+ 1162→ (megabuf(b0 + 4) = 0.0001;
+ 1163→ megabuf(b0 + 5) = chroma;
+ 1164→ angk = rand(k * 5.19 + 0.7 + beats * 13.7) * tau;
+ 1165→ spdk = 0.4 + rand(k * 9.71 + 2 + beats * 7.31) * 1.2 + bass * 0.4;
+ 1166→ megabuf(b0) = cos(angk) * 0.03;
+ 1167→ megabuf(b0 + 1) = sin(angk) * 0.03;
+ 1168→ megabuf(b0 + 2) = cos(angk) * spdk;
+ 1169→ megabuf(b0 + 3) = sin(angk) * spdk;
+ 1170→ budget = budget - 1),
+ 1171→ 0)
+ 1172→age = megabuf(b0 + 4)
+ 1173→if(above(age, 0),
+ 1174→ (drag = exp(-age * 1.6);
+ 1175→ megabuf(b0) = megabuf(b0) + megabuf(b0 + 2) * drag * dt;
+ 1176→ megabuf(b0 + 1) = megabuf(b0 + 1) + megabuf(b0 + 3) * drag * dt;
+ 1177→ megabuf(b0 + 4) = age + dt;
+ 1178→ px = megabuf(b0);
+ 1179→ py = megabuf(b0 + 1);
+ 1180→ hue = megabuf(b0 + 5);
+ 1181→ sat = min(1, age * 3.5);
+ 1182→ lum = max(0, exp(-age * 1.15));
+ 1183→ if(below(lum, 0.02), (megabuf(b0 + 4) = -1; skip = 1), 0);
+ 1184→ x = px / aspect;
+ 1185→ y = py;
+ 1186→ red = hsvr(hue, sat, lum);
+ 1187→ green = hsvg(hue, sat, lum);
+ 1188→ blue = hsvb(hue, sat, lum);
+ 1189→ size = 0.6 + lum * 3.2;
+ 1190→ alpha = min(1, lum * 2)),
+ 1191→ (skip = 1))
+ 1192→`,
+ 1193→ },
+ 1194→ {
+ 1195→ folder: "root/embedded/Zai-Magic",
+ 1196→ name: "Tunnel Vision",
+ 1197→ author: "Zai",
+ 1198→ description:
+ 1199→ "A tunnel of rings where every ring carries the hue of the beat it was born on -- each kick sends a new color wave riding out from the center. Hand-authored for OVE.",
+ 1200→ content: `name: "Tunnel Vision"
+ 1201→author: "Zai"
+ 1202→mode: lines
+ 1203→blend: additive
+ 1204→persist: 0.9
+ 1205→linewidth: 2
+ 1206→
+ 1207→[init]
+ 1208→rings = 26
+ 1209→per = 36
+ 1210→n = rings * per
+ 1211→tt = 0
+ 1212→chroma = 0.4
+ 1213→
+ 1214→[frame]
+ 1215→t = t + dt
+ 1216→tt = tt + dt * (0.14 + bass_att * 0.22)
+ 1217→chroma = frac(chroma + beat * 0.115)
+ 1218→pulse = exp(-beatage * 4)
+ 1219→
+ 1220→[point]
+ 1221→ring = floor(i * rings)
+ 1222→ri = frac(i * rings)
+ 1223→dot = floor(ri * per)
+ 1224→z = frac(ring / rings + tt)
+ 1225→rad = 0.06 + pow(z, 1.7) * 1.05
+ 1226→a = dot / per * tau + ring * 0.24 + sin(t * 0.5) * 0.15
+ 1227→wob = 1 + sin(ri * tau * 5 + t * 2) * 0.05 * treb_att + pulse * 0.08
+ 1228→x = sin(a) * rad * wob / aspect
+ 1229→y = cos(a) * rad * wob
+ 1230→hue = chroma - z * 0.38
+ 1231→lum = pow(z, 0.6) * (0.35 + vol_att * 0.55) + pulse * 0.15 * z
+ 1232→red = hsvr(hue, 0.92, min(1, lum))
+ 1233→green = hsvg(hue, 0.92, min(1, lum))
+ 1234→blue = hsvb(hue, 0.92, min(1, lum))
+ 1235→skip = if(above(dot, per - 1.5), 1, 0)
+ 1236→`,
+ 1237→ },
+ 1238→ {
+ 1239→ folder: "root/embedded/Zai-Magic",
+ 1240→ name: "Lissajous Loom",
+ 1241→ author: "Zai",
+ 1242→ description:
+ 1243→ "A lissajous weave that re-threads its frequency ratio once per 4-beat bar while the rainbow rotates on every single beat -- the pattern morphs in color and shape together. Hand-authored for OVE.",
+ 1244→ content: `name: "Lissajous Loom"
+ 1245→author: "Zai"
+ 1246→mode: lines
+ 1247→blend: additive
+ 1248→persist: 0.91
+ 1249→linewidth: 1.5
+ 1250→
+ 1251→[init]
+ 1252→n = 1400
+ 1253→mode = 0
+ 1254→chroma = 0.5
+ 1255→
+ 1256→[frame]
+ 1257→t = t + dt * 0.5
+ 1258→fa = 2 + floor(mode / 2) + equal(mode, 4) * 2
+ 1259→fb = 3 + mode % 2 + equal(mode, 3)
+ 1260→pulse = exp(-beatage * 3)
+ 1261→
+ 1262→[beat]
+ 1263→if(equal(beats % 4, 0), mode = (mode + 1) % 5, 0)
+ 1264→chroma = frac(chroma + 0.09)
+ 1265→
+ 1266→[point]
+ 1267→ph = i * tau
+ 1268→x = sin(ph * fa + t) * (0.55 + bass * 0.28 + pulse * 0.1) / aspect
+ 1269→y = sin(ph * fb + t * 1.35 + 1.1) * (0.55 + mid * 0.25)
+ 1270→hue = chroma + i * 0.33 + sin(ph + t) * 0.05
+ 1271→lum = 0.3 + abs(v) * 0.55 + vol_att * 0.35 + pulse * 0.2
+ 1272→red = hsvr(hue, 0.9, min(1, lum))
+ 1273→green = hsvg(hue, 0.9, min(1, lum))
+ 1274→blue = hsvb(hue, 0.9, min(1, lum))
+ 1275→alpha = 0.55 + 0.45 * sin(ph * fa)
+ 1276→`,
+ 1277→ },
+ 1278→ {
+ 1279→ folder: "root/embedded/Zai-Magic",
+ 1280→ name: "Beat Matrix",
+ 1281→ author: "Zai",
+ 1282→ description:
+ 1283→ "A 16-band spectrum wall -- every beat rotates the entire rainbow across the columns and whitewashes the lit cells for one strobe flash. Hand-authored for OVE (direct getspec bands).",
+ 1284→ content: `name: "Beat Matrix"
+ 1285→author: "Zai"
+ 1286→mode: dots
+ 1287→blend: additive
+ 1288→persist: 0.74
+ 1289→linewidth: 2
+ 1290→
+ 1291→[init]
+ 1292→cols = 16
+ 1293→rows = 10
+ 1294→n = cols * rows
+ 1295→chroma = 0
+ 1296→
+ 1297→[frame]
+ 1298→t = t + dt
+ 1299→chroma = frac(chroma + beat * 0.166)
+ 1300→flash = exp(-beatage * 5)
+ 1301→
+ 1302→[point]
+ 1303→col = floor(i * cols)
+ 1304→row = floor(frac(i * cols) * rows)
+ 1305→lvl = getspec(col / cols, 0, 0)
+ 1306→frombottom = (rows - 1 - row) / rows
+ 1307→lit = if(below(frombottom, lvl * 1.15), 1, 0)
+ 1308→x = (col / (cols - 1) * 1.74 - 0.87) / aspect
+ 1309→y = 0.9 - row * 0.19
+ 1310→hue = chroma + col / cols * 0.78
+ 1311→lum = lit * (0.3 + bass_att * 0.3) + 0.045
+ 1312→sat = 0.9 - flash * 0.85
+ 1313→red = hsvr(hue, sat, lum + flash * lit * 0.55)
+ 1314→green = hsvg(hue, sat, lum + flash * lit * 0.55)
+ 1315→blue = hsvb(hue, sat, lum + flash * lit * 0.55)
+ 1316→size = 2.4 + lit * 1.7 + treb * 1.2
+ 1317→`,
+ 1318→ },
+ 1319→]
+ 1320→
\ No newline at end of file
diff --git a/tool-results/v711-01-fresh-home.png b/tool-results/v711-01-fresh-home.png
new file mode 100644
index 0000000..d635b63
Binary files /dev/null and b/tool-results/v711-01-fresh-home.png differ
diff --git a/tool-results/v711-02-fresh-demo.png b/tool-results/v711-02-fresh-demo.png
new file mode 100644
index 0000000..ba4f3fb
Binary files /dev/null and b/tool-results/v711-02-fresh-demo.png differ
diff --git a/tool-results/v711-03-preset-loaded.png b/tool-results/v711-03-preset-loaded.png
new file mode 100644
index 0000000..361052c
Binary files /dev/null and b/tool-results/v711-03-preset-loaded.png differ
diff --git a/tool-results/v711-04-demo-with-manual.png b/tool-results/v711-04-demo-with-manual.png
new file mode 100644
index 0000000..92002dd
Binary files /dev/null and b/tool-results/v711-04-demo-with-manual.png differ
diff --git a/tool-results/v711-05-demo-custom-material.png b/tool-results/v711-05-demo-custom-material.png
new file mode 100644
index 0000000..b4f6c8e
Binary files /dev/null and b/tool-results/v711-05-demo-custom-material.png differ
diff --git a/tool-results/v711-06-seq-unchecked.png b/tool-results/v711-06-seq-unchecked.png
new file mode 100644
index 0000000..21c4f70
Binary files /dev/null and b/tool-results/v711-06-seq-unchecked.png differ
diff --git a/tool-results/v711-07-state-check.png b/tool-results/v711-07-state-check.png
new file mode 100644
index 0000000..56a252f
Binary files /dev/null and b/tool-results/v711-07-state-check.png differ
diff --git a/tool-results/v711-08-whats-new.png b/tool-results/v711-08-whats-new.png
new file mode 100644
index 0000000..a34faf2
Binary files /dev/null and b/tool-results/v711-08-whats-new.png differ
diff --git a/tool-results/v711-09-decoded.png b/tool-results/v711-09-decoded.png
new file mode 100644
index 0000000..e36b0da
Binary files /dev/null and b/tool-results/v711-09-decoded.png differ
diff --git a/tool-results/v711-10-imported.png b/tool-results/v711-10-imported.png
new file mode 100644
index 0000000..d9b96e1
Binary files /dev/null and b/tool-results/v711-10-imported.png differ
diff --git a/tool-results/v711-11-saved-named.png b/tool-results/v711-11-saved-named.png
new file mode 100644
index 0000000..b15e337
Binary files /dev/null and b/tool-results/v711-11-saved-named.png differ
diff --git a/tool-results/v711-12-empty-name-dialog.png b/tool-results/v711-12-empty-name-dialog.png
new file mode 100644
index 0000000..8fda4d4
Binary files /dev/null and b/tool-results/v711-12-empty-name-dialog.png differ
diff --git a/tool-results/v711-13-renamed.png b/tool-results/v711-13-renamed.png
new file mode 100644
index 0000000..2167236
Binary files /dev/null and b/tool-results/v711-13-renamed.png differ
diff --git a/tool-results/v711-14-rename-conflict.png b/tool-results/v711-14-rename-conflict.png
new file mode 100644
index 0000000..676764d
Binary files /dev/null and b/tool-results/v711-14-rename-conflict.png differ
diff --git a/tool-results/v711-15-empty-name-1.png b/tool-results/v711-15-empty-name-1.png
new file mode 100644
index 0000000..a7654ca
Binary files /dev/null and b/tool-results/v711-15-empty-name-1.png differ
diff --git a/tool-results/v711-16-suffix-conflict.png b/tool-results/v711-16-suffix-conflict.png
new file mode 100644
index 0000000..43dc2f3
Binary files /dev/null and b/tool-results/v711-16-suffix-conflict.png differ
diff --git a/tool-results/v711-17-named-dup.png b/tool-results/v711-17-named-dup.png
new file mode 100644
index 0000000..0f4f5f0
Binary files /dev/null and b/tool-results/v711-17-named-dup.png differ
diff --git a/tool-results/v711-18-default-boot.png b/tool-results/v711-18-default-boot.png
new file mode 100644
index 0000000..06439a0
Binary files /dev/null and b/tool-results/v711-18-default-boot.png differ
diff --git a/tool-results/v711-19-default-demo.png b/tool-results/v711-19-default-demo.png
new file mode 100644
index 0000000..a2ce1ff
Binary files /dev/null and b/tool-results/v711-19-default-demo.png differ
diff --git a/tool-results/v711-20-final-fresh.png b/tool-results/v711-20-final-fresh.png
new file mode 100644
index 0000000..a718f3a
Binary files /dev/null and b/tool-results/v711-20-final-fresh.png differ
diff --git a/tool-results/v711-21-mobile.png b/tool-results/v711-21-mobile.png
new file mode 100644
index 0000000..bb55d47
Binary files /dev/null and b/tool-results/v711-21-mobile.png differ
diff --git a/tool-results/v711-22-desktop.png b/tool-results/v711-22-desktop.png
new file mode 100644
index 0000000..30689b0
Binary files /dev/null and b/tool-results/v711-22-desktop.png differ
diff --git a/worklog.md b/worklog.md
index a1af4ad..85dca13 100755
--- a/worklog.md
+++ b/worklog.md
@@ -2213,3 +2213,82 @@ BROWSER E2E (agent-browser + VLM, ALL PASS):
Stage Summary:
- v7.7 shipped: scripts are now shareable as single Unicode strings — ~half the length of the script, self-checking, copy/paste-survivable, lossless for ANY text (compiling not required) — and the Settings tab is decluttered while the house account keeps Stream-a-Link working for everyone. The shared dialog height cap protects every dialog app-wide from tall content on short screens.
+
+---
+Task ID: 37
+Agent: main (Z.ai Code)
+Task: v7.10.1 (user report while testing their new "Spectrum Bars" script): a section on the very right of spectrum visuals is inactive and never reacts to the music -- same dead right edge on other spectrum scripts. Explain why, then fix it engine-side so every script benefits.
+
+DIAGNOSIS (why the right edge is dead):
+- getspec()/sv mapped bar position 0..1 across the WHOLE FFT: bin = 2 + floor(p^1.6 * 4093) at 8192-point FFT, so pos 1.0 = bin 4095 = Nyquist (22.05/24 kHz). pos 0.8 already lands at ~16.8 kHz.
+- Real music carries almost nothing up there, and every lossy source low-passes even lower: the demo collection is 192 kbps MP3 @48 kHz (LAME low-pass ~18.6 kHz), YouTube AAC ~15.5 kHz. Measured on demo-01 (20 s, 8192-FFT): 0.000% of total magnitude above 18.6 kHz; content above -40 dB rel. peak ends at ~13.4 kHz, above -60 dB at ~17.8 kHz.
+- So bar positions above ~0.82 read byte 0 every frame -- the rightmost ~20% of every spectrum visual was permanently frozen at the floor. Not a script bug: every script sampling getspec over 0..1 (all 10 converted getspec presets included) showed the same dead edge, exactly like the Winamp AVS originals. The engine's own flux detector already caps at 16 kHz for the same reason (audio.ts getBands).
+
+Work Log:
+- src/lib/ava/audio.ts: new SPEC_TOP_HZ = 16000 const (with the full why-comment); getSpectrum() now maps the field from bin 2 (~12 Hz) to the 16 kHz bin instead of Nyquist: hiBin = clamp(ceil(16000/binHz)), bin = 2 + floor(p^1.6 * (hiBin - 2)). Same log-ish curve, every position now lands on frequencies music occupies; right edge = 12-16 kHz "air" band (cymbals/hats/sparkle). Matches the flux cap.
+- rust-agent/src/spectrum.rs: SPEC_TOP_HZ const + spectrum_at() mirrors the new mapping exactly (source parity; not compiled locally, no cargo).
+- help-panel.tsx: sv entry now states the span: "spectrum magnitude 0..1 (≈12 Hz to 16 kHz, log-ish -- the range music actually occupies)".
+- version.ts 7.10.1 + history entry; release-notes.ts 7.10.1 "Spectrum edge unfrozen" (fixed, first-person voice). lint clean; tsc clean apart from the pre-existing base-target share-codec BigInt literals.
+
+VERIFICATION (three independent measurements):
+- Offline full-track emulation (Blackman window + AnalyserNode dB byte mapping, every 21 ms of demo-01): under the NEW mapping every one of 48 bars is >15/255 in 76-100% of frames and >60/255 in 68-99% -- nothing is structurally dead. Under the old mapping positions >0.82 read literal zero.
+- Fresh in-page chain (eval: fetch demo-01 -> decodeAudioData -> own AnalyserNode at t=150 s): top positions dance (pos 1.0 = 16 kHz fluctuates 4..118/255 frame to frame).
+- Live E2E with the USER'S OWN "Spectrum Bars" script (found in the test browser's localStorage from the previous session, 28 bars x 4 pts, running on Demo Track 2 at 60 FPS): per-bar pixel measurements across 6 frames show the rightmost bars (f 0.74-1.0 = 9.9-16 kHz) swinging 4%..76% with the music -- under the old mapping those bars sampled 16.8-24 kHz and sat pinned at 0 (the script's own below(lvl,0.004)?0 snap would hold them perfectly flat). Also verified with a purpose-built 48-bar "Edge Check" script earlier in the session.
+- Anomaly chased and closed: one 5-sample window showed bars 36-48 at floor while bass danced -- turned out to be the demo track's outro (offline analysis: t=187-191 s has a legitimately dead 10-16 kHz band while lows keep going); the demo ended right after (SRC: IDLE, place advanced), matching the timeline. Not a mapping issue.
+- What's New dialog: v7.10.1 "Spectrum edge unfrozen" first with CURRENT badge + FIXED tag; drift guard silent after a clean reload (one transient guard warning was hot-reload event ordering + stale console history); the one hydration error was agent-browser's own data-__ab-ci instrumentation, absent on a clean reload. Console otherwise clean; dev.log clean.
+- Test-browser localStorage cleared afterwards (convention); scratch PCM deleted.
+
+Stage Summary:
+- v7.10.1 shipped: the frozen right edge of every spectrum visual is unfrozen. The bar field now spans ~12 Hz to 16 kHz (the range music actually occupies) instead of stretching to Nyquist, so the right bars land on the 10-16 kHz air band and dance with the hats/cymbals. The user's own Spectrum Bars script (whose 0.78+0.45*f treble tilt now has something real to boost) verified live: rightmost bars swing 4..76%.
+
+---
+Task ID: 38
+Agent: main (Z.ai Code)
+Task: v7.11 (user request, three parts). RESTORED RETROACTIVELY — the entry was lost in a session cutoff (same as Task 35 once was); scope reconstructed from the version.ts 7.11 history and the tool-results/v711-* evidence screenshots (v711-01 through v711-22, all PASS). (1) Mobile wake lock: while any audio source is live the site holds a Screen Wake Lock so phone/tablet screens stay on; released the moment the engine goes idle so normal inactivity dimming returns. (2) A respectful Demo Track button: no longer force-engages the Sequence checkbox when the visitor has loaded a preset (tree double-click / Load / copied preset) or brought their own script material into the editor — Demo just plays the track; the Zai-Magic showcase only starts for a visit that never picked or typed anything. (3) Script tab one-click Save: a valid script shows the Save button with no save target needed; auto-names from the script's name header (a hash import of "Spectrum Bars:[▼…]" saves as "Spectrum Bars"), "Empty Name" when nameless, " (1)"/" (2)"… suffixes on conflicts — and when the name had to be invented, a "Give This Preset a Name" dialog opens to rename the just-saved preset in one step (saved FIRST, renamed second, so nothing is ever lost).
+
+Work Log (reconstructed):
+- src/lib/ava/wake-lock.ts (NEW): Screen Wake Lock sentinel manager — acquire on source-live, release on idle, visibilitychange re-acquire (browsers silently drop the sentinel on hide), silent degradation where unsupported.
+- audio-source.ts / control-panel / page.tsx: per-visit manualChoice flag + boot-script snapshot decide whether the Demo showcase may engage; ensureDefaultPreset gained the same guard (mic/file/link never clobber a custom draft either). Demo teardown paths (ended/stop/switch) all respect it.
+- script-tab.tsx: Save button visible on any valid script with no target; auto-naming ladder (script name header -> hash import label -> "Empty Name" -> conflict suffixes); "Give This Preset a Name" dialog after any invented save. Hash⇢Script imports clear the stale save target and pass the decoded label through as the fallback name.
+- version.ts 7.11 + history; release-notes.ts 7.11 entry.
+
+E2E (evidence: tool-results/v711-01..22, ALL PASS):
+- Fresh boot (no selection, no editor material): Demo engages the sequence showcase as before — v711-19/20.
+- Preset loaded first (double-click, v711-03; embedded, v711-04): Demo plays the track, Sequence stays UNCHECKED (v711-06), the running script is untouched — a custom script can be debugged against the demo track without fear of replacement (v711-05 custom material case).
+- Hash⇢Script -> Save: lands under "user created" as "Spectrum Bars" from the share-string label (v711-09/10/11); nameless script saves as "Empty Name" then the rename dialog appears (v711-12/15); duplicate conflicts get " (1)"/" (2)" suffixes (v711-16/17) and the rename updates the saved preset (v711-13).
+- Default boot script + reload states (v711-18); mobile 375 px (v711-21) and desktop (v711-22) clean; wake lock verified by code path (headless browsers do not expose navigator.wakeLock — degrade-silent branch exercised).
+
+Stage Summary:
+- v7.11 shipped: the screen stays on while music plays, the Demo button stopped hijacking the preset selection, and one click saves any valid script with a smart name. This restored entry closes the gap in the build diary.
+
+---
+Task ID: 39
+Agent: main (Z.ai Code)
+Task: v7.12 (user request): add the classic-AVS background-movement family to the scripting — the background shifts up/down/left/right, spins clockwise/counter-clockwise, draws in and blows out, with the speed accelerating to the beat and hard hits flipping the spin direction while the fade to black stays slow (so colors blend) — then ship a permanent Zai-Magic preset "osc-rotations": a stationary centered oscilloscope whose left/right points color-shift together and whose center points hold their own hue, over a background rotating cw/ccw to the music with temporary beat acceleration.
+
+Work Log:
+- src/lib/ava/engine.ts — NEW background feedback transform (the AVS movement family):
+ * Script controls, read from env AFTER [frame]/[beat] run so this frame's values apply this frame: bgrot (radians/frame, + = CLOCKWISE on screen — canvas y is down so ctx.rotate(+) spins cw), bgx/bgy (per-frame drift, the -1..1 screen convention, y is UP matching script x/y), bgzoom (per-frame scale: <1 draw-in, >1 blow-out, 1 = neutral). Values persist in env like any variable — set from [init] (persists forever), [frame] (every frame) or [beat] (on-beat jumps); neutral readings skip the pass entirely (zero cost for every preset that doesn't move).
+ * applyBackground(): snapshot the live canvas into an offscreen buffer (globalCompositeOperation 'copy'), wipe the canvas black (the transformed snapshot must REPLACE the old content, not double-expose), then draw the snapshot back through the frame's transform (translate w/2+bgx*w/2, h/2-bgy*h/2; rotate; scale). Uncovered regions (rotated corners, slid-off edges) read black and the persistence fade blends the trails — exactly how the classic AVS plugins made "speed accelerates to the beat, a hard hit flips the spin, fade stays slow" work: the rate is just a script variable driven by a beatage envelope, and a [beat] section flips a dir variable on hard hits (bass vs bass_att).
+ * Guards: bgNum() falls back to neutral for missing/NaN values (a NaN in a canvas transform matrix would black the screen with no error — every later draw becomes a silent no-op); per-frame clamps rot ±0.6 rad, drift ±0.25, zoom 0.5..2. Buffer recreated automatically on resize; null until first use (zero cost otherwise). Gated on this.script so a stale env after clearScript can never spin a blank canvas.
+- src/lib/ava/embedded-presets.ts — eleventh Zai-Magic preset "osc-rotations" (user-named, lowercase so it sorts last in the tree): persist 0.965 (slow fade — colors blend), [frame] drives bgrot = dir * (0.0032 + punch * (0.014 + vol_att * 0.012)) with punch = exp(-beatage*3.2) (temporary beat acceleration that relaxes with the kick ring), bgzoom = 1 - 0.0009 - punch*0.0011 (a whisper of draw-in so trails spiral inward instead of ringing), hue drifts dt*0.012 + beat*0.02; [beat] flips dir on hard hits only (bass > 1.25 * bass_att — soft beats leave the spin alone); [point] draws the stationary centered scope x = i*2-1, y = v*(0.3+vol_att*0.8) with mirror-symmetric color h = hue + |i-0.5|*2 * 0.34 (points equidistant left/right of center share one hue, the center holds its own). EMBEDDED_SEED_VERSION 7.10 -> 7.12 (route re-seeds the subtree; fingerprint self-heal would have caught it too). "ten presets" comments updated to eleven (both the file-top and the Zai-Magic section header).
+- src/components/ava/help-panel.tsx — bgrot / bgx/bgy / bgzoom rows in the variables table + three example formulas (beat-accelerated spin, hard-hit direction flip in [beat], draw-in).
+- src/lib/version.ts 7.12 + history; src/lib/release-notes.ts 7.12 entry (2x added, first-person voice). Share codec deliberately untouched: its dictionary is a frozen compatibility contract (adding entries needs a new scheme marker) — bg* identifiers ride the pair packing like any custom variable.
+
+HEADLESS VALIDATION (/home/z/tmp-validate/bg-transform/validate-osc.ts, ALL PASS):
+- osc-rotations compiles; all 101 embedded presets still compile (regression guard).
+- Compiled-program behavior: quiet frame bgrot 0.00397 rad/frame cw + bgzoom 0.99906; hard beat bgrot 0.0256 (6.5x) AND dir flips to -1; soft beat no flip; point pass x spans ±1 with |y| symmetric, mirrored positions share hue exactly (0.7906 both) while center is distinct (0.6206).
+
+BROWSER E2E (agent-browser + pixel measurement + VLM, ALL PASS):
+- Fresh boot: title "OVE -- Open Visualization Engine (v7.12)"; tree shows "Zai-Magic -- 11"; /api/presets re-seeded on the version gate and serves osc-rotations (106 presets total; sorts last in Zai-Magic as designed).
+- Preset live on the demo collection (manual load first, then Demo Track — v7.11's respectful-Demo still holds: Random/Sequence stayed UNCHECKED with a preset loaded): footer "SCRIPT: osc-rotations · Demo Track 1/2 · FPS 59-60 · PTS 512 · BPM 132.3/138.8 locked". Mid-band (scope zone) mean 133/255; outer zones lit only by rotation-carried trails.
+- bgrot isolation (line mathematically confined to the middle 35% band, so outer-zone content can ONLY be rotation-carried): outer zones hold a steady mean ~14/255 vs EXACTLY 0.00 in the no-bg control — rotation puts content where the line cannot reach.
+- bgx sign: +0.012 drifts the bright cloud right (mean x 0.518); -0.012 mirrors it exactly (0.480) — the sign convention verified by symmetry.
+- bgy sign: +0.02 pulls the bright cloud UP (mean y-from-top 0.368 vs 0.5 neutral).
+- bgzoom: at 1.05/frame the outer zones went from dead 0.00 to ~38/255 (blow-out pushes the confined line's trails outward); at 1.003/frame with persist 0.92 trails die before traveling far (consistent with the fade math — equilibrium analysis, not a defect).
+- What's New dialog: 7.12 "Background movement (AVS-style) + osc-rotations" first with CURRENT badge, drift guard silent. Help panel: bg* rows + examples all present.
+- VLM on the live screenshot: horizontal oscillating waveform line at vertical center; large soft colorful trail masses around it; hue variation green at the outer left/right edges, yellow mid, red/pink at the center — the mirror-symmetric color design visibly realized; no defects; "60 FPS . 512 PTS" overlay normal.
+- Console clean (0 errors/0 warnings); dev.log clean (59 successful requests, no compile errors). Footer: sticks to the viewport bottom on short pages (1280x800 Beat tab: docH 800, footer bottom 800) and pushes to the document end on long pages (375 px Presets tab: docH 1469, footer at doc end, no overlap); 375 px scrollWidth 375 = clientWidth (no horizontal overflow). Test-browser localStorage cleared afterwards.
+
+Stage Summary:
+- v7.12 shipped: scripts can move the whole picture — bgrot spins, bgx/bgy slide, bgzoom draws in / blows out — with the speed riding the music exactly the way the classic AVS plugins did it (beatage envelopes drive the rate, [beat] flips the spin on hard hits, persist keeps the fade slow so colors blend). The eleventh Zai-Magic preset "osc-rotations" demonstrates the full recipe: a stationary centered oscilloscope with mirror-symmetric color (left/right shift together, center holds its own hue) over a beat-accelerated, hard-hit-flipping rotating background.