c123beee-6c07-4660-8af8-d2ff1f89767c
This commit is contained in:
parent
5451c6ccdd
commit
b54841fabf
9 changed files with 1048 additions and 21 deletions
|
|
@ -22,7 +22,7 @@ const FRAME_VARS: [string, string][] = [
|
||||||
['beatage', 'seconds since the last beat (0 on the beat frame) -- exp(-beatage*4) is a kick envelope'],
|
['beatage', 'seconds since the last beat (0 on the beat frame) -- exp(-beatage*4) is a kick envelope'],
|
||||||
['w / h', 'canvas size in pixels'],
|
['w / h', 'canvas size in pixels'],
|
||||||
['aspect', 'canvas width / height -- divide x by it for perfect circles'],
|
['aspect', 'canvas width / height -- divide x by it for perfect circles'],
|
||||||
['persist', 'motion-trail persistence 0..0.98 (writable per frame)'],
|
['persist', 'motion-trail persistence 0..1, writable per frame -- 1 = the framebuffer never fades (old content then leaves only through a bg* transform or a blend: replace repaint)'],
|
||||||
['bgrot', 'background spin in radians per frame -- + clockwise, - counter-clockwise; 0 = off (set in [init] / [frame] / [beat]; persists like any variable)'],
|
['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'],
|
['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'],
|
['bgzoom', 'background scale per frame -- below 1 draw-in (shrinks toward the center), above 1 blow-out (expands); 1 = off'],
|
||||||
|
|
@ -167,8 +167,8 @@ name: My Visualization
|
||||||
author: you
|
author: you
|
||||||
description: shown in the preset list
|
description: shown in the preset list
|
||||||
mode: lines # lines | dots | solid
|
mode: lines # lines | dots | solid
|
||||||
blend: additive # additive | alpha
|
blend: additive # additive | alpha | multiply | screen | subtract | replace
|
||||||
persist: 0.85 # trail persistence 0..0.98
|
persist: 0.85 # trail persistence 0..1 (1 = never fades)
|
||||||
linewidth: 2
|
linewidth: 2
|
||||||
|
|
||||||
[init] # runs once on load
|
[init] # runs once on load
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,16 @@
|
||||||
// Owns the requestAnimationFrame loop:
|
// Owns the requestAnimationFrame loop:
|
||||||
// audio bands -> beat tracker -> env -> [frame] -> [beat] -> [point]xN -> draw
|
// audio bands -> beat tracker -> env -> [frame] -> [beat] -> [point]xN -> draw
|
||||||
//
|
//
|
||||||
// Drawing supports three modes (lines | dots | solid) with additive blending,
|
// Drawing supports three modes (lines | dots | solid), six blend composites
|
||||||
// per-point colors, a two-pass glow, and a persistence fade for motion trails.
|
// (additive | alpha | multiply | screen | subtract | replace), per-point
|
||||||
|
// colors, a two-pass glow, and a persistence fade for motion trails
|
||||||
|
// (persist 1 = no fade at all -- v7.14.1).
|
||||||
|
|
||||||
import { AudioEngine } from './audio'
|
import { AudioEngine } from './audio'
|
||||||
import { BeatTracker } from './beat'
|
import { BeatTracker } from './beat'
|
||||||
import { AVA_HOOKS, resetEelBuffers } from './expression'
|
import { AVA_HOOKS, resetEelBuffers } from './expression'
|
||||||
import type { AvaScript } from './script'
|
import type { AvaScript } from './script'
|
||||||
import type { BandConfig, BandLevels, BeatConfig, BeatFrame } from './types'
|
import type { BandConfig, BandLevels, BeatConfig, BeatFrame, BlendMode } from './types'
|
||||||
import { DEFAULT_BAND_CONFIG, DEFAULT_BEAT_CONFIG } from './types'
|
import { DEFAULT_BAND_CONFIG, DEFAULT_BEAT_CONFIG } from './types'
|
||||||
|
|
||||||
const MAX_POINTS = 8192
|
const MAX_POINTS = 8192
|
||||||
|
|
@ -25,6 +27,23 @@ function bgNum(v: number | undefined, neutral: number): number {
|
||||||
return Number.isFinite(x) ? x : neutral
|
return Number.isFinite(x) ? x : neutral
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** v7.14.1 -- blend header -> Canvas2D composite operation for the content
|
||||||
|
* pass. 'subtract' maps to canvas 'difference' (|dst - src|): Canvas2D has
|
||||||
|
* no clamped true-subtract op; difference is the classic approximation and
|
||||||
|
* shares the property the mode exists for -- it can never climb to white.
|
||||||
|
* 'replace' also composites source-over, but with the core pass at FULL
|
||||||
|
* alpha (see the draw section) so fresh strokes overwrite the pixels under
|
||||||
|
* them instead of stacking -- the pairing that makes a persist-1 feedback
|
||||||
|
* blow-out hold its color instead of saturating. */
|
||||||
|
const BLEND_COMPOSITE: Record<BlendMode, GlobalCompositeOperation> = {
|
||||||
|
additive: 'lighter',
|
||||||
|
alpha: 'source-over',
|
||||||
|
multiply: 'multiply',
|
||||||
|
screen: 'screen',
|
||||||
|
subtract: 'difference',
|
||||||
|
replace: 'source-over',
|
||||||
|
}
|
||||||
|
|
||||||
export interface EngineStats {
|
export interface EngineStats {
|
||||||
fps: number
|
fps: number
|
||||||
levels: BandLevels
|
levels: BandLevels
|
||||||
|
|
@ -242,12 +261,19 @@ export class AvaEngine {
|
||||||
const ctx = this.ctx
|
const ctx = this.ctx
|
||||||
|
|
||||||
// ---- persistence fade -------------------------------------------------
|
// ---- persistence fade -------------------------------------------------
|
||||||
|
// v7.14.1: persist may now reach 1.0 -- a true zero-fade framebuffer.
|
||||||
|
// At exactly 1 the fade fill is skipped entirely: old content leaves the
|
||||||
|
// screen only through a bg* transform carrying it off (or by being
|
||||||
|
// repainted, blend 'replace'). That is what makes "colors ride outward
|
||||||
|
// forever while the fresh drawing replaces the center" possible.
|
||||||
const meta = this.script?.meta
|
const meta = this.script?.meta
|
||||||
const persistBase = meta ? meta.persist : 0.85
|
const persistBase = meta ? meta.persist : 0.85
|
||||||
const persist = Math.min(Math.max(this.env.persist ?? persistBase, 0), 0.985)
|
const persist = Math.min(Math.max(this.env.persist ?? persistBase, 0), 1)
|
||||||
ctx.globalCompositeOperation = 'source-over'
|
ctx.globalCompositeOperation = 'source-over'
|
||||||
|
if (persist < 1) {
|
||||||
ctx.fillStyle = `rgba(0,0,0,${(1 - persist).toFixed(4)})`
|
ctx.fillStyle = `rgba(0,0,0,${(1 - persist).toFixed(4)})`
|
||||||
ctx.fillRect(0, 0, w, h)
|
ctx.fillRect(0, 0, w, h)
|
||||||
|
}
|
||||||
|
|
||||||
// ---- audio + beat -----------------------------------------------------
|
// ---- audio + beat -----------------------------------------------------
|
||||||
const levels = this.audio.getBands(this.bandConfig, dt)
|
const levels = this.audio.getBands(this.bandConfig, dt)
|
||||||
|
|
@ -356,13 +382,19 @@ export class AvaEngine {
|
||||||
const halfH = h / 2
|
const halfH = h / 2
|
||||||
|
|
||||||
if (n > 0 && meta) {
|
if (n > 0 && meta) {
|
||||||
ctx.globalCompositeOperation = meta.blend === 'additive' ? 'lighter' : 'source-over'
|
// v7.14.1: the blend header now selects any of the six composites;
|
||||||
|
// 'replace' additionally gets its core pass at full alpha below.
|
||||||
|
const replace = meta.blend === 'replace'
|
||||||
|
ctx.globalCompositeOperation = BLEND_COMPOSITE[meta.blend]
|
||||||
|
|
||||||
if (meta.mode === 'lines') {
|
if (meta.mode === 'lines') {
|
||||||
ctx.lineCap = 'round'
|
ctx.lineCap = 'round'
|
||||||
ctx.lineJoin = 'round'
|
ctx.lineJoin = 'round'
|
||||||
for (let pass = 0; pass < 2; pass++) {
|
for (let pass = 0; pass < 2; pass++) {
|
||||||
const gain = pass === 0 ? 0.22 : 0.9
|
// replace: halo slightly stronger (it converges toward the source
|
||||||
|
// color, never past it), core at FULL alpha -- the stroke owns its
|
||||||
|
// pixels outright. Every other mode keeps the glow+core gains.
|
||||||
|
const gain = pass === 0 ? (replace ? 0.25 : 0.22) : replace ? 1 : 0.9
|
||||||
let pen = false
|
let pen = false
|
||||||
let pr = 0, pg = 0, pb = 0, pa = 0, pxp = 0, pyp = 0, plw = 0
|
let pr = 0, pg = 0, pb = 0, pa = 0, pxp = 0, pyp = 0, plw = 0
|
||||||
for (let k = 0; k < n; k++) {
|
for (let k = 0; k < n; k++) {
|
||||||
|
|
@ -396,7 +428,7 @@ export class AvaEngine {
|
||||||
} else if (meta.mode === 'dots') {
|
} else if (meta.mode === 'dots') {
|
||||||
for (let pass = 0; pass < 2; pass++) {
|
for (let pass = 0; pass < 2; pass++) {
|
||||||
const sizeGain = pass === 0 ? 2.6 : 1
|
const sizeGain = pass === 0 ? 2.6 : 1
|
||||||
const alphaGain = pass === 0 ? 0.18 : 0.85
|
const alphaGain = pass === 0 ? (replace ? 0.25 : 0.18) : replace ? 1 : 0.85
|
||||||
for (let k = 0; k < n; k++) {
|
for (let k = 0; k < n; k++) {
|
||||||
const o = k * POINT_STRIDE
|
const o = k * POINT_STRIDE
|
||||||
if (pts[o + 5]) continue
|
if (pts[o + 5]) continue
|
||||||
|
|
@ -439,7 +471,7 @@ export class AvaEngine {
|
||||||
const r = Math.max(0, Math.min(1, sr / count))
|
const r = Math.max(0, Math.min(1, sr / count))
|
||||||
const g = Math.max(0, Math.min(1, sg / count))
|
const g = Math.max(0, Math.min(1, sg / count))
|
||||||
const b = Math.max(0, Math.min(1, sb / count))
|
const b = Math.max(0, Math.min(1, sb / count))
|
||||||
const a = Math.max(0, Math.min(1, sa / count)) * 0.65
|
const a = Math.max(0, Math.min(1, sa / count)) * (replace ? 1 : 0.65)
|
||||||
ctx.fillStyle = `rgba(${(r * 255) | 0},${(g * 255) | 0},${(b * 255) | 0},${a.toFixed(3)})`
|
ctx.fillStyle = `rgba(${(r * 255) | 0},${(g * 255) | 0},${(b * 255) | 0},${a.toFixed(3)})`
|
||||||
ctx.fill()
|
ctx.fill()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@
|
||||||
// name: Gap The Mind <- header keys before the first [section]
|
// name: Gap The Mind <- header keys before the first [section]
|
||||||
// author: synth-c (adapted)
|
// author: synth-c (adapted)
|
||||||
// mode: lines <- lines | dots | solid
|
// mode: lines <- lines | dots | solid
|
||||||
// blend: additive <- additive | alpha
|
// blend: additive <- additive | alpha | multiply | screen | subtract | replace
|
||||||
// persist: 0.9 <- motion trail persistence 0..0.98
|
// persist: 0.9 <- motion trail persistence 0..1 (1 = never fades)
|
||||||
// linewidth: 2
|
// linewidth: 2
|
||||||
//
|
//
|
||||||
// [init] runs once when the script loads
|
// [init] runs once when the script loads
|
||||||
|
|
@ -58,7 +58,11 @@ export interface AvaScript {
|
||||||
const SECTION_NAMES = new Set(['init', 'frame', 'beat', 'point'])
|
const SECTION_NAMES = new Set(['init', 'frame', 'beat', 'point'])
|
||||||
const HEADER_KEYS = new Set(['name', 'author', 'description', 'mode', 'blend', 'persist', 'linewidth', 'ysign'])
|
const HEADER_KEYS = new Set(['name', 'author', 'description', 'mode', 'blend', 'persist', 'linewidth', 'ysign'])
|
||||||
const MODES = new Set(['lines', 'dots', 'solid'])
|
const MODES = new Set(['lines', 'dots', 'solid'])
|
||||||
const BLENDS = new Set(['additive', 'alpha'])
|
// v7.14.1: the composite family. 'subtract' = canvas 'difference'
|
||||||
|
// (|dst - src|, the AVS subtract approximation); 'replace' = source-over
|
||||||
|
// with the core pass at full alpha (fresh strokes overwrite -- pairs with
|
||||||
|
// persist 1 for zero-fade feedback blow-outs).
|
||||||
|
const BLENDS = new Set(['additive', 'alpha', 'multiply', 'screen', 'subtract', 'replace'])
|
||||||
|
|
||||||
const DEFAULT_META: ScriptMeta = {
|
const DEFAULT_META: ScriptMeta = {
|
||||||
name: 'Untitled',
|
name: 'Untitled',
|
||||||
|
|
@ -159,16 +163,19 @@ function tryParse(text: string): { script: AvaScript | null; meta?: ScriptMeta;
|
||||||
break
|
break
|
||||||
case 'blend':
|
case 'blend':
|
||||||
if (!BLENDS.has(value)) {
|
if (!BLENDS.has(value)) {
|
||||||
return { script: null, meta: { ...meta }, error: { message: `blend must be 'additive' or 'alpha' (got '${value}')`, section: 'header', line: lineNo } }
|
return { script: null, meta: { ...meta }, error: { message: `blend must be one of: additive, alpha, multiply, screen, subtract, replace (got '${value}')`, section: 'header', line: lineNo } }
|
||||||
}
|
}
|
||||||
meta.blend = value as BlendMode
|
meta.blend = value as BlendMode
|
||||||
break
|
break
|
||||||
case 'persist': {
|
case 'persist': {
|
||||||
const v = parseFloat(value)
|
const v = parseFloat(value)
|
||||||
if (Number.isNaN(v)) {
|
if (Number.isNaN(v)) {
|
||||||
return { script: null, meta: { ...meta }, error: { message: `persist must be a number 0..0.98 (got '${value}')`, section: 'header', line: lineNo } }
|
return { script: null, meta: { ...meta }, error: { message: `persist must be a number 0..1 (got '${value}')`, section: 'header', line: lineNo } }
|
||||||
}
|
}
|
||||||
meta.persist = Math.min(Math.max(v, 0), 0.98)
|
// v7.14.1: 1.0 is legal -- a true zero-fade framebuffer (the engine
|
||||||
|
// then skips the fade pass entirely; old content only leaves via a
|
||||||
|
// bg* transform carrying it off-screen).
|
||||||
|
meta.persist = Math.min(Math.max(v, 0), 1)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'linewidth': {
|
case 'linewidth': {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
// AVA — Advanced Visualization Agent · shared types
|
// AVA — Advanced Visualization Agent · shared types
|
||||||
|
|
||||||
export type RenderMode = 'lines' | 'dots' | 'solid'
|
export type RenderMode = 'lines' | 'dots' | 'solid'
|
||||||
export type BlendMode = 'additive' | 'alpha'
|
/** v7.14.1 — the blend header grew from additive|alpha to the full composite
|
||||||
|
* family. 'subtract' is the AVS subtract approximation (canvas 'difference',
|
||||||
|
* |dst - src|); 'replace' draws source-over with the core pass at FULL alpha
|
||||||
|
* so fresh strokes overwrite the pixels under them (never accumulates to
|
||||||
|
* white, pairs with persist 1 for zero-fade feedback effects). */
|
||||||
|
export type BlendMode = 'additive' | 'alpha' | 'multiply' | 'screen' | 'subtract' | 'replace'
|
||||||
export type AudioSourceKind = 'idle' | 'demo' | 'mic' | 'file' | 'link'
|
export type AudioSourceKind = 'idle' | 'demo' | 'mic' | 'file' | 'link'
|
||||||
|
|
||||||
/** Fine-tunable band detection configuration (Hz ranges + gains). */
|
/** Fine-tunable band detection configuration (Hz ranges + gains). */
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,20 @@ export interface ReleaseNote {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RELEASE_NOTES: ReleaseNote[] = [
|
export const RELEASE_NOTES: ReleaseNote[] = [
|
||||||
|
{
|
||||||
|
version: '7.14.1',
|
||||||
|
name: 'New blend modes + zero-fade trails',
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
kind: 'added',
|
||||||
|
text: 'Scripts can now choose how fresh strokes mix with what is already on screen: blend: multiply, screen, subtract and replace join the old additive and alpha. replace is the interesting one -- new lines overwrite the color underneath instead of stacking on it, so a figure redrawn over its own trails keeps its color instead of blooming to white.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: 'added',
|
||||||
|
text: 'persist: 1 is now legal: a framebuffer that never fades. Combined with replace and the background zoom it finally makes the classic "colors ride outward forever" blow-out work -- the blow-out itself carries old color off screen while the fresh drawing replaces it in the center, at constant speed, no fading.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
version: '7.14',
|
version: '7.14',
|
||||||
name: 'Arctic Rainbow re-sprayed',
|
name: 'Arctic Rainbow re-sprayed',
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,29 @@
|
||||||
// warns in the console when the two drift apart.
|
// warns in the console when the two drift apart.
|
||||||
//
|
//
|
||||||
// History:
|
// History:
|
||||||
|
// 7.14.1 — Script engine: blend modes + zero-fade persistence (user
|
||||||
|
// request, Brainstorm preset session). The renderer's content
|
||||||
|
// pass could only composite additively ('lighter') or plain
|
||||||
|
// alpha; the persistence header was hard-capped at 0.98, so a
|
||||||
|
// feedback blow-out ALWAYS faded -- the user wanted colors that
|
||||||
|
// ride outward forever at full brightness, replaced at the center
|
||||||
|
// by new color. Blend header extended to six values: multiply,
|
||||||
|
// screen, subtract (canvas 'difference' |dst-src| -- Canvas2D has
|
||||||
|
// no clamped true-subtract op; difference is the classic
|
||||||
|
// approximation and can never climb to white) and replace
|
||||||
|
// (source-over with the CORE pass at FULL alpha -- fresh strokes
|
||||||
|
// overwrite the pixels under them instead of stacking, so a
|
||||||
|
// figure redrawn over its own accumulating trails keeps its color
|
||||||
|
// instead of saturating to white; halo pass 0.25, dots core
|
||||||
|
// alpha 1.0, solid fill alpha 1.0). persist header + env override
|
||||||
|
// widened 0..0.98 -> 0..1; at exactly 1 the fade fill is SKIPPED
|
||||||
|
// entirely (engine.ts). The pairing this was asked for: blend
|
||||||
|
// replace + persist 1 + the v7.12 bgzoom/bgrot feedback = a
|
||||||
|
// static-speed blow-out that never fades, the fresh drawing
|
||||||
|
// replacing the old color at the center -- the blow-out itself is
|
||||||
|
// the cleanup. Help panel + parser error strings updated; no
|
||||||
|
// library changes (existing presets keep additive/alpha and their
|
||||||
|
// behavior byte-for-byte).
|
||||||
// 7.14 — Arctic Rainbow re-imagined (user request): the old stand-in was
|
// 7.14 — Arctic Rainbow re-imagined (user request): the old stand-in was
|
||||||
// plain white columns of rain dots sweeping upward. Rebuilt as a
|
// plain white columns of rain dots sweeping upward. Rebuilt as a
|
||||||
// music-driven water spray, stateless projectile physics in the
|
// music-driven water spray, stateless projectile physics in the
|
||||||
|
|
@ -450,4 +473,4 @@
|
||||||
|
|
||||||
export const APP_NAME = 'Open Visualization Engine'
|
export const APP_NAME = 'Open Visualization Engine'
|
||||||
export const APP_SHORT = 'OVE'
|
export const APP_SHORT = 'OVE'
|
||||||
export const APP_VERSION = '7.14'
|
export const APP_VERSION = '7.14.1'
|
||||||
|
|
|
||||||
454
tool-results/read_1793921017322_9c38b72c422b.txt
Normal file
454
tool-results/read_1793921017322_9c38b72c422b.txt
Normal file
|
|
@ -0,0 +1,454 @@
|
||||||
|
1→// OVE — app identity + version stamp.
|
||||||
|
2→//
|
||||||
|
3→// Single source of truth for the product name and version shown in the UI
|
||||||
|
4→// (header, footer, document title). Bump APP_VERSION whenever we ship a
|
||||||
|
5→// large change so the running build can always be identified at a glance —
|
||||||
|
6→// useful both for the z.ai Version History and for naming local backups
|
||||||
|
7→// (v6.0 = rename to Open Visualization Engine + version indicator).
|
||||||
|
8→//
|
||||||
|
9→// STANDING RULE (since v7.6): every APP_VERSION bump MUST come with a
|
||||||
|
10→// matching entry at the top of RELEASE_NOTES in src/lib/release-notes.ts —
|
||||||
|
11→// that list powers the header's "What's New!" dialog. A dev-mode guard
|
||||||
|
12→// warns in the console when the two drift apart.
|
||||||
|
13→//
|
||||||
|
14→// History:
|
||||||
|
15→// 7.14 — Arctic Rainbow re-imagined (user request): the old stand-in was
|
||||||
|
16→// plain white columns of rain dots sweeping upward. Rebuilt as a
|
||||||
|
17→// music-driven water spray, stateless projectile physics in the
|
||||||
|
18→// [point] program: each of up to 120 droplets is a deterministic
|
||||||
|
19→// parabola (own apex height 1.26..1.88, own flight time 1.9..3.4 s,
|
||||||
|
20→// phase-staggered clock; every splash cycle re-rolls its origin,
|
||||||
|
21→// drift and arch) -- launch from below the bottom edge, slow rise,
|
||||||
|
22→// hang, arch left or right, fall back off-screen. Drop count
|
||||||
|
23→// follows the music (vol_att/bass_att envelope, fast attack 6/s,
|
||||||
|
24→// slow release 1.2/s: silence = 0 drops, loud = all 120). Each
|
||||||
|
25→// drop owns a hue that shifts by its own per-drop amount on every
|
||||||
|
26→// detected beat, plus a subtle gradient along its tail. The tail
|
||||||
|
27→// is 12 explicit trail samples (0.014..0.44 s back along the arc)
|
||||||
|
28→// drawn as a tapered polyline in lines mode, gated by smoothstep
|
||||||
|
29→// on the fall speed: nothing while rising (crisp dot), fading in
|
||||||
|
30→// as the arch turns downward -- no persistence smear (persist
|
||||||
|
31→// 0.12). Slot map through cell centers (the v7.13.1 lesson), 14
|
||||||
|
32→// slots per drop (separator + head + 12 trail), n = 1680.
|
||||||
|
33→// EMBEDDED_SEED_VERSION 7.13.1 -> 7.14 re-seeds the library.
|
||||||
|
34→// 7.13.1 — Beat Matrix endpoint fix (user report): the rightmost column
|
||||||
|
35→// of dots sat one row short -- its bottom dot missing -- while a
|
||||||
|
36→// phantom dot spilled one column past the wall's right edge at
|
||||||
|
37→// the top. Root cause: the engine's point position i spans 0..1
|
||||||
|
38→// INCLUSIVE (classic AVS Superscope convention, engine.ts: i =
|
||||||
|
39→// k/(n-1)), but Beat Matrix's grid math assumed exclusive i = k/n:
|
||||||
|
40→// floor(i*cols) turned the last point into col 16 (one past the
|
||||||
|
41→// 16-column wall) at row 0, and column 15 was left with nine
|
||||||
|
42→// dots (rows 0-8, no bottom). Fix: the script now maps through
|
||||||
|
43→// cell centers -- cell = i*(cols*rows-1)+0.5, col =
|
||||||
|
44→// floor(cell/rows), row = floor(cell-col*rows) -- which puts
|
||||||
|
45→// every floor() half a cell away from an integer boundary, so
|
||||||
|
46→// the endpoint can never spill and float dust can never shuffle
|
||||||
|
47→// a dot. Full 16x10 bijection verified headlessly (all 160
|
||||||
|
48→// points in 160 distinct cells, col<=15, row<=9, bottom-right
|
||||||
|
49→// cell present). Sweep of the other 100 presets: every other
|
||||||
|
50→// floor(i*M) use is benign -- angular presets close their loop
|
||||||
|
51→// (bar=bars lands exactly on bar 0's angle), Aurora Veil's
|
||||||
|
52→// phantom lands off-screen (x=1.19), the megabuf particle
|
||||||
|
53→// presets draw their phantom slot at alpha 0, and the converted
|
||||||
|
54→// Winamp classics rendered identically under original AVS
|
||||||
|
55→// (which also uses inclusive i). EMBEDDED_SEED_VERSION 7.12 ->
|
||||||
|
56→// 7.13.1 re-seeds the library so every visitor gets the fixed
|
||||||
|
57→// Beat Matrix.
|
||||||
|
58→// 7.13 — one build, every feature: the parallel development lines are
|
||||||
|
59→// merged. This tree now carries BOTH the community OVE-Directory
|
||||||
|
60→// (v7.11 here) AND the parallel line's additions — the spectrum
|
||||||
|
61→// 16 kHz cap (its 7.10.1), the screen wake lock + respectful
|
||||||
|
62→// Demo button + one-click Save (its 7.11, folded into this
|
||||||
|
63→// history as 7.10.2 to keep one clean timeline), and the
|
||||||
|
64→// AVS-style background movement family (bgrot / bgx / bgy /
|
||||||
|
65→// bgzoom) with the eleventh Zai-Magic preset "osc-rotations"
|
||||||
|
66→// (its 7.12). Git three-way merge against the shared v7.10
|
||||||
|
67→// ancestor (identical tree hashes verified on both sides); the
|
||||||
|
68→// only hand-merged surface was the Script tab toolbar, which
|
||||||
|
69→// now carries the one-click Save and "Upload to OVE-Directory"
|
||||||
|
70→// side by side, and the two changelogs (here + release-notes).
|
||||||
|
71→// 7.12 — background movement + osc-rotations. Scripts can now move the
|
||||||
|
72→// whole framebuffer every frame, the way the classic Winamp AVS
|
||||||
|
73→// plugins did: bgrot (radians/frame, + clockwise on screen),
|
||||||
|
74→// bgx / bgy (per-frame drift in the -1..1 screen convention, y
|
||||||
|
75→// up), bgzoom (< 1 draw-in, > 1 blow-out) -- read after [frame]/
|
||||||
|
76→// [beat] so this frame's values apply this frame. The engine
|
||||||
|
77→// snapshots the canvas into an offscreen buffer, wipes, and
|
||||||
|
78→// draws the snapshot back through the transform, so the
|
||||||
|
79→// persistence fade blends the moving trails: "speed accelerates
|
||||||
|
80→// to the beat, a hard hit flips the spin, fade to black stays
|
||||||
|
81→// slow" is pure script (a beatage envelope drives the rate; a
|
||||||
|
82→// [beat] section flips a dir variable on hard hits via bass vs
|
||||||
|
83→// bass_att). Neutral readings (0/0/0/1) skip the pass entirely
|
||||||
|
84→// (zero cost for every preset that doesn't move); non-finite
|
||||||
|
85→// values fall back to neutral (a NaN in a canvas transform
|
||||||
|
86→// matrix would black the screen with no error); per-frame
|
||||||
|
87→// clamps: rot ±0.6 rad, drift ±0.25, zoom 0.5..2. New 11th
|
||||||
|
88→// Zai-Magic preset "osc-rotations" (user-requested): a
|
||||||
|
89→// stationary centered oscilloscope (time-domain v) whose color
|
||||||
|
90→// is mirror-symmetric -- points equidistant left/right of
|
||||||
|
91→// center shift together, the center points hold their own hue
|
||||||
|
92→// -- over a background rotating cw/ccw to the music that
|
||||||
|
93→// temporarily accelerates on the beat; hard hits
|
||||||
|
94→// (bass > 1.25 × bass_att) flip the spin; a whisper of draw-in
|
||||||
|
95→// (bgzoom ~0.999) spirals the trails inward; persist 0.965
|
||||||
|
96→// keeps the fade slow so colors blend. Help panel: bg* variable
|
||||||
|
97→// rows + three new example formulas. EMBEDDED_SEED_VERSION
|
||||||
|
98→// 7.10 -> 7.12 (library re-seed adds the preset; the share
|
||||||
|
99→// codec's dictionary stays frozen per its compatibility
|
||||||
|
100→// contract -- bg* identifiers ride the pair packing like any
|
||||||
|
101→// custom variable). Headless validation of the preset's
|
||||||
|
102→// compiled programs: quiet 0.004 rad/frame, hard beat 0.026
|
||||||
|
103→// (6.5×) + direction flip, soft beat no flip, mirrored points
|
||||||
|
104→// share hue / center distinct
|
||||||
|
105→// 7.11 — the community OVE-Directory. A sixth tab (Directory) lists every
|
||||||
|
106→// script published to the community directory — a separate VPS at
|
||||||
|
107→// s3374dir.openvisengine.com, reached ONLY through this server's
|
||||||
|
108→// backend proxy (/api/ove-directory/*), which holds the API key in
|
||||||
|
109→// env and re-validates every upload (the share string must decode
|
||||||
|
110→// AND compile). Publishing: the Script tab gains "Upload to
|
||||||
|
111→// OVE-Directory" (enabled only while the editor shows ✔ Compiled OK
|
||||||
|
112→// with no runtime error); the script travels as its Script⇢Hash
|
||||||
|
113→// string, so the directory stores a fraction of the source. The
|
||||||
|
114→// listing carries each entry's name, type and date, a 1–5 star
|
||||||
|
115→// widget (the voter id is an HttpOnly cookie minted by the proxy;
|
||||||
|
116→// one vote per script per visitor, enforced upstream), the live
|
||||||
|
117→// "Average: #.#" to the right of the stars, and sorting by Name /
|
||||||
|
118→// Type / Date / Rating (done upstream, both directions). "Get"
|
||||||
|
119→// fetches the full record, decodes the hash back into the exact
|
||||||
|
120→// script, files it in this browser's preset tree under
|
||||||
|
121→// root/user created/OVE-Directory (content-deduped, names numbered
|
||||||
|
122→// "(1) (2) (3)…" on collision) and offers "Open in editor".
|
||||||
|
123→// Duplicate uploads resolve as success (409 DUPLICATE_HASH carries
|
||||||
|
124→// the existing row); duplicate votes show the stored aggregates.
|
||||||
|
125→// 7.10.2 — wake lock, a respectful Demo button, one-click save. While any
|
||||||
|
126→// audio source is live the site holds a Screen Wake Lock so phone
|
||||||
|
127→// and tablet screens stay on (released the moment the engine goes
|
||||||
|
128→// idle so the normal inactivity dimming returns; browsers silently
|
||||||
|
129→// drop the sentinel on hide, so a visibilitychange watcher
|
||||||
|
130→// re-acquires it on return while music is still going;
|
||||||
|
131→// unsupported browsers degrade silently). The Demo Track button no
|
||||||
|
132→// longer force-engages the Sequence checkbox: a visitor who loaded
|
||||||
|
133→// a preset (tree double-click / Load / Copy to my presets / created
|
||||||
|
134→// one), brought their own script material into the editor (typed,
|
||||||
|
135→// pasted, Hash⇢Script, file), saved their own default startup
|
||||||
|
136→// script, or runs their own Random/Sequence mix keeps their
|
||||||
|
137→// selection -- Demo just plays the track (the showcase's tree
|
||||||
|
138→// re-scope, 20 s interval and retire-on-end all skip too; a new
|
||||||
|
139→// per-visit manualChoice flag + a boot-script snapshot decide, and
|
||||||
|
140→// ensureDefaultPreset gained the same guard so mic/file/link never
|
||||||
|
141→// clobber a custom draft either; the showcase now only starts for
|
||||||
|
142→// a visit that never picked or typed anything). Script tab: a valid
|
||||||
|
143→// script shows the Save button with NO save target needed -- one
|
||||||
|
144→// click creates a preset under "user created", auto-named from the
|
||||||
|
145→// script's name: header (a hash import of "Spectrum Bars:[▼…]"
|
||||||
|
146→// saves as "Spectrum Bars"), "Empty Name" when nameless, " (1)"/
|
||||||
|
147→// " (2)"… suffixes on conflicts, and whenever the name had to be
|
||||||
|
148→// invented a "Give This Preset a Name" dialog opens to rename the
|
||||||
|
149→// just-saved preset in one step (the preset is always saved FIRST,
|
||||||
|
150→// renamed second, so nothing is ever lost to a naming problem).
|
||||||
|
151→// Hash⇢Script imports clear the stale save target (the editor now
|
||||||
|
152→// holds foreign content) and pass the decoded label through as the
|
||||||
|
153→// save-name fallback when the script has no name: header
|
||||||
|
154→// 7.10.1 — the frozen right edge of spectrum visuals. getspec()/sv mapped
|
||||||
|
155→// the bar field across the whole FFT (bin 2 ... Nyquist, ~12 Hz to
|
||||||
|
156→// 24 kHz), but music carries almost nothing above ~16 kHz and every
|
||||||
|
157→// lossy source low-passes even lower (192 kbps MP3 at ~18.6 kHz,
|
||||||
|
158→// YouTube AAC at ~15.5 kHz; measured across the demo collection:
|
||||||
|
159→// 0.000% of total magnitude above 18.6 kHz), so the rightmost ~20%
|
||||||
|
160→// of every spectrum visual read a permanent zero -- bars that never
|
||||||
|
161→// moved. getSpectrum() now tops the field out at 16 kHz
|
||||||
|
162→// (SPEC_TOP_HZ, matching the spectral-flux cap in getBands()), so
|
||||||
|
163→// the right edge lands on the 12-16 kHz "air" band where cymbals,
|
||||||
|
164→// hats and synth sparkle live. rust-agent's spectrum_at() mirrors
|
||||||
|
165→// the change, and the help panel's sv entry now states the span.
|
||||||
|
166→// 7.10 — ten demo tracks + first-person changelog + declutter. The demo
|
||||||
|
167→// source is now a COLLECTION: ten mastered tracks in
|
||||||
|
168→// public/demo-tracks/ (demo-01.mp3 … demo-10.mp3, filename order
|
||||||
|
169→// 1→10, replacing the single Neural Mecha Pulse file). Each Demo
|
||||||
|
170→// click plays the visitor's PLACE in the collection once through;
|
||||||
|
171→// the place advances when a pass truly ends — natural finish or
|
||||||
|
172→// the Stop button ('ended'/'stop' teardown reasons, a new
|
||||||
|
173→// onDemoEnded signature) — and wraps 10→1; switching to another
|
||||||
|
174→// source keeps the place. The place persists per visitor in
|
||||||
|
175→// localStorage (ove.demoTrack.next), so returning picks up where
|
||||||
|
176→// they left off. The engine caches one decoded buffer at a time,
|
||||||
|
177→// keyed by track index (~50–60 MB PCM each — all ten would be half
|
||||||
|
178→// a gigabyte). The header chip + footer SRC label show the current
|
||||||
|
179→// track's name (Demo Track N). RELEASE_NOTES rewritten in the
|
||||||
|
180→// operator's first-person voice (no more third-person marketing
|
||||||
|
181→// copy or addressing the visitor), and every em dash in the
|
||||||
|
182→// interface replaced with a plain double minus ( -- ) — including
|
||||||
|
183→// the embedded preset library's descriptions and script comments
|
||||||
|
184→// (EMBEDDED_SEED_VERSION 7.01 → 7.10 re-seed). Declutter: the
|
||||||
|
185→// landing menu's source list line, the presets panel's two
|
||||||
|
186→// read-only paragraphs, the Random/Sequence hint line, the empty
|
||||||
|
187→// user-folder note reworded, the Settings theme/highlight/Native
|
||||||
|
188→// paragraphs, the link dialog's privacy footnote and the footer's
|
||||||
|
189→// engine-ok dot are all gone — compile errors still toast, so the
|
||||||
|
190→// always-green badge was noise
|
||||||
|
191→// 7.9 — footer credit + What's New polish. The footer's blank middle now
|
||||||
|
192→// carries the maker's signature: "© {year} · Site Created &
|
||||||
|
193→// Deployed by Sum'ting White" (year generated from the clock,
|
||||||
|
194→// hydration-guarded) with a "View my portfolio at zcreations.dev"
|
||||||
|
195→// link that opens zcreations.dev in a new tab — centered in the
|
||||||
|
196→// blank area from xl (mx-auto between the stats and the version
|
||||||
|
197→// stamp), dropping to its own centered row on narrower screens so
|
||||||
|
198→// nothing ever crowds. The What's New dialog got a typographic
|
||||||
|
199→// tidy-up: a hairline divider above every release box
|
||||||
|
200→// (border-zinc-800 — a ramp step, so it matches every base theme
|
||||||
|
201→// through the globals.css remaps), and each change row became an
|
||||||
|
202→// invisible two-column grid — the kind tag top-right justified
|
||||||
|
203→// against the column boundary, the description top-left with word
|
||||||
|
204→// wrap — so all descriptions start on the same pixel column
|
||||||
|
205→// 7.8 — share strings 2.0: ~72% shorter than the script (was ~49%). The
|
||||||
|
206→// base-62 idea, cranked to 11,172… 32,164: after the symbol
|
||||||
|
207→// dictionary (which grew ~170 data-mined entries — every function
|
||||||
|
208→// WITH its open paren, every engine variable, the AVS idioms
|
||||||
|
209→// "hue = hue + dt * " and "t = t + dt * ", " * 0.", ") * ",
|
||||||
|
210→// blank-line+section skeletons, "linewidth: " / "blend: additive"
|
||||||
|
211→// headers, ~70 number literals, and the library's common
|
||||||
|
212→// variable names incl. chroma) the whole body is read as ONE BIG
|
||||||
|
213→// NUMBER and rewritten in base 32,164 — one digit per character
|
||||||
|
214→// from the CJK + Hangul blocks, ~15 bits a character vs base-62's
|
||||||
|
215→// 5.95 (so "1400" costs ~0.85 of a character). The decoder gets
|
||||||
|
216→// the body's alphabet back from a ~32-character bitmap header
|
||||||
|
217→// instead of ~80 literal symbols. Short scripts where the header
|
||||||
|
218→// wouldn't pay keep the v7.7 symbol form (encoder picks whichever
|
||||||
|
219→// is shorter); ▲ strings from v7.7 still decode; still lossless
|
||||||
|
220→// for ANY text, still copy-safe, still self-checking. Corpus
|
||||||
|
221→// measure: median share string is 27% of the script's length
|
||||||
|
222→// (best 22%), e.g. Bass Bloom 1341 → 326 characters
|
||||||
|
223→// 7.7 — script sharing + settings declutter. Script⇢Hash / Hash⇢Script:
|
||||||
|
224→// two buttons in the Script tab turn the editor's script into ONE
|
||||||
|
225→// short shareable Unicode string ("Bass Bloom:[▲…①①①①①①①①]") and
|
||||||
|
226→// paste one back — the script returns exactly as written (lossless
|
||||||
|
227→// for ANY text, compiling NOT required, so WIP scripts can be
|
||||||
|
228→// shared for debugging). The string is built from built-in symbol
|
||||||
|
229→// references (♤ = line return, ♡ = beats, ♠ = beatage, ♦ = space,
|
||||||
|
230→// ♩♪♫♬ = the four sections, one symbol per function/variable, plus
|
||||||
|
231→// a pair-packing layer that folds leftover identifier/number text
|
||||||
|
232→// two chars per CJK code point) — ~50% shorter than the script on
|
||||||
|
233→// real presets. Copy-safe by construction (no raw whitespace or
|
||||||
|
234→// control chars; platform-injected variation selectors / zero-
|
||||||
|
235→// widths stripped on paste) and self-checking: 8 check digits (an
|
||||||
|
236→// FNV-1a digest rendered as ①–⑯) reject strings that were cut off
|
||||||
|
237→// by a character limit or altered in transit, instead of decoding
|
||||||
|
238→// into garbage. Also: the Music Services log in / "log out of all"
|
||||||
|
239→// section in Settings is parked (MUSIC_SERVICES_UI flag) — the
|
||||||
|
240→// built-in house account already powers Stream-a-Link for
|
||||||
|
241→// everyone; the section returns reworked in a later update
|
||||||
|
242→// 7.6.1 — header declutter: the Set Beat button is gone from the header
|
||||||
|
243→// (tap-tempo lives in the Beat tab, and the Space shortcut works
|
||||||
|
244→// anywhere — both unchanged). The beat indicator dot stays.
|
||||||
|
245→// 7.6 — "What's New!" dialog: a Sparkles button in the header (left of
|
||||||
|
246→// the theme switch) opens the visitor-facing changelog — every
|
||||||
|
247→// release from v4.0 on, newest first, each with one-line Added /
|
||||||
|
248→// Improved / Fixed / Removed notes (src/lib/release-notes.ts,
|
||||||
|
249→// color-coded tags, the running version badged "current",
|
||||||
|
250→// scrollable with the thin scrollbar). A small emerald dot on the
|
||||||
|
251→// button marks updates the visitor hasn't seen yet — per visitor
|
||||||
|
252→// in localStorage, cleared on open (mount-guarded, no hydration
|
||||||
|
253→// mismatch). Standing rule added: version bumps ship with their
|
||||||
|
254→// release note; a dev-mode guard warns when they drift
|
||||||
|
255→// 7.5.1 — Link dialog width fix: text no longer escapes the dialog box.
|
||||||
|
256→// Root cause: Radix dialogs lay their children in an implicit CSS
|
||||||
|
257→// grid whose single column sizes to the widest child's min-content;
|
||||||
|
258→// the RECENT LINKS rows carry truncate spans (white-space: nowrap),
|
||||||
|
259→// and Chromium measures a nowrap flex item's full text width for
|
||||||
|
260→// that measurement — so a long saved title (e.g. "Bobby Foxx — This
|
||||||
|
261→// Is Why We Can't Have Nice Things (Official Music Video)") blew
|
||||||
|
262→// the column ~40 px past the 448 px dialog, and EVERY child (title,
|
||||||
|
263→// description, input + Load, rows, footer) laid out beyond the
|
||||||
|
264→// right border. Fix: the shared Dialog/AlertDialog content now pins
|
||||||
|
265→// the column to minmax(0,1fr) — capped at the available width, so
|
||||||
|
266→// no nowrap content can ever stretch a dialog again (covers every
|
||||||
|
267→// dialog app-wide) — the Link dialog widened to max-w-lg for
|
||||||
|
268→// comfier wrapping, and min-w-0 + break-words added on the
|
||||||
|
269→// recents/playlist containers and error text as belt-and-braces
|
||||||
|
270→// 7.5 — yt-dlp compatibility: single tracks resolve by DOWNLOAD + CONVERT
|
||||||
|
271→// instead of proxying the extractor's stream URL. The old stream
|
||||||
|
272→// URLs were IP-locked (googlevideo bakes in the resolver's IP),
|
||||||
|
273→// expired within hours, and arrived as webm/opus — a container
|
||||||
|
274→// Safari & co. refuse ("The media resource … was not suitable").
|
||||||
|
275→// The server now runs the operator's pipeline per track:
|
||||||
|
276→// yt-dlp -f bestaudio/best -x --audio-format mp3
|
||||||
|
277→// --audio-quality 0 -o "<dir>/%(title)s.%(ext)s" URL
|
||||||
|
278→// — audio-only download, universal MP3 at VBR-0 (~245 kbps), the
|
||||||
|
279→// video's real title preserved for the Playing: label. The
|
||||||
|
280→// finished file streams same-origin with full Range support from
|
||||||
|
281→// a per-track workspace dir (30-min sliding TTL + orphan sweeper);
|
||||||
|
282→// /api/audio/stream gained a file-serving mode. Hardening around
|
||||||
|
283→// it: shared failure classifier for both yt-dlp passes (metadata +
|
||||||
|
284→// download), 180 s download budget, 300 MB cap, bot-wall/stale-
|
||||||
|
285→// extractor answers unchanged, ffmpeg-missing degrades to the old
|
||||||
|
286→// proxy path (logged once), queue look-ahead lead 15 s → 60 s so
|
||||||
|
287→// playlist transitions don't stall behind conversions
|
||||||
|
288→// 7.4 — Sequence mode: a second mix checkbox next to Random. Ticked, it
|
||||||
|
289→// cycles the scope folder's presets IN ORDER (the tree's exact
|
||||||
|
290→// top-to-bottom order) instead of shuffling — and when it moves
|
||||||
|
291→// past the last preset it wraps back to the first, so the folder
|
||||||
|
292→// loops forever. Random and Sequence are mutually exclusive
|
||||||
|
293→// (ticking either one unticks the other; both share the same
|
||||||
|
294→// delay and folder scope), Random now never loads the same preset
|
||||||
|
295→// twice in a row, and Sequence holds its breath through silence
|
||||||
|
296→// exactly like Random does. The Demo showcase now runs SEQUENCE —
|
||||||
|
297→// the one play-through walks every Zai-Magic preset in order
|
||||||
|
298→// (20 s each) rather than repeating random picks, and when the
|
||||||
|
299→// demo stops, whichever mode it checked is unchecked again
|
||||||
|
300→// 7.3 — demo-flow refinements: the Demo Track is a single pass — it plays
|
||||||
|
301→// "Neural Mecha Pulse" once through and stops (a demo, not a radio
|
||||||
|
302→// station). However it ends (natural finish, Stop, or switching to
|
||||||
|
303→// another source) the showcase unwinds: the randomizer the Demo
|
||||||
|
304→// flow force-checked is unchecked again, and the visitor lands
|
||||||
|
305→// back on the "Select an audio source" overlay. Preset-change
|
||||||
|
306→// popups are gone ("Preset loaded" / "Random preset" / "Default
|
||||||
|
307→// preset running") — the footer's SCRIPT: label already echoes
|
||||||
|
308→// every change, so the toasts were pure intrusion; only compile
|
||||||
|
309→// errors still toast. Club-mode randomizer now respects silence:
|
||||||
|
310→// when no music is playing it never re-shuffles the selected
|
||||||
|
311→// preset, even with the checkbox on — it waits for the next track
|
||||||
|
312→// 7.2.1 — Supernova (Zai-Magic) preset fix: the particle kill condition
|
||||||
|
313→// (|px|+|py| > 2.4) was physically unreachable — drag caps a
|
||||||
|
314→// particle's total travel at vel/1.6 ≈ 1.0, so nothing ever
|
||||||
|
315→// crossed 1.4, nothing ever died, and since respawning requires a
|
||||||
|
316→// dead particle, the per-beat "budget = n" detonation could never
|
||||||
|
317→// fire after the initial one — the preset exploded once on load,
|
||||||
|
318→// then faded to permanent black. Now every beat force-clears the
|
||||||
|
319→// whole 620-particle field in [beat] (guaranteed full re-detonation
|
||||||
|
320→// on the very same frame), fully-cooled particles die by fade so
|
||||||
|
321→// long beat gaps leave a gentle ember drizzle instead of black,
|
||||||
|
322→// and each blast re-randomizes its geometry (no two explosions
|
||||||
|
323→// alike). Embedded library re-seeded (seed marker 7.01)
|
||||||
|
324→// 7.2 — new-visitor showcase flow. Defaults for first-time visitors:
|
||||||
|
325→// the Black + Purple Outline theme with the Blue highlight
|
||||||
|
326→// (previously royal purple + yellow), and Zai-Magic "Bass Bloom"
|
||||||
|
327→// as the startup preset (previously the first Synth-C classic).
|
||||||
|
328→// The idle overlay's Demo Track button now carries an animated
|
||||||
|
329→// reflective shimmer (skewed light band sweeping left→right every
|
||||||
|
330→// 3.2 s, reduced-motion safe) to draw the click — and that one
|
||||||
|
331→// click starts the full showcase: the demo track plays while the
|
||||||
|
332→// preset tree selects + expands Zai-Magic, randomize switches on
|
||||||
|
333→// scoped to that folder with a 20-second change interval. Music
|
||||||
|
334→// started with NO preset active (own draft running / selection
|
||||||
|
335→// cleared) auto-loads Bass Bloom. Preset changes — manual load,
|
||||||
|
336→// the random mix, the Demo flow — no longer hard-cut: the engine
|
||||||
|
337→// snapshots the outgoing frame and blur-dissolves it into the new
|
||||||
|
338→// preset over 700 ms (progressive blur + easeOutCubic fade + a
|
||||||
|
339→// slight scale-up so the softening edges never gap; plain
|
||||||
|
340→// crossfade where ctx.filter is unsupported)
|
||||||
|
341→// 7.1 — real demo track + a beat detector that survives real music.
|
||||||
|
342→// The Demo button now plays "Neural Mecha Pulse" (the operator's
|
||||||
|
343→// mastered industrial-EDM banger, public/demo-track.mp3, loops
|
||||||
|
344→// gaplessly) instead of the synthetic techno pattern — a real
|
||||||
|
345→// recording is the honest test: deep sub for the bass bands,
|
||||||
|
346→// driving mids, crisp highs, and a kick buried in sustained
|
||||||
|
347→// sub-bass. That last part exposed the 6.x detector as fundamentally
|
||||||
|
348→// broken on mastered music: a LEVEL threshold can never see the
|
||||||
|
349→// kick through the sub (peak-to-mean caps at ~1.2× → sporadic
|
||||||
|
350→// fires, phantom 120-BPM locks from the minInterval floor).
|
||||||
|
351→// v7.1 rewrites detection the way real beat trackers work
|
||||||
|
352→// (aubio/librosa-style): a full-band spectral-flux ONSET envelope
|
||||||
|
353→// → autocorrelation over 300–1000 ms lags with a log-BPM prior →
|
||||||
|
354→// comb-filter phase with grid hysteresis → beats fired on an
|
||||||
|
355→// absolute grid kept glued by a PLL pull. Headlessly validated
|
||||||
|
356→// against the actual track PCM (AnalyserNode chain replicated
|
||||||
|
357→// frame-for-frame): locks the true ~133 BPM (the song drifts
|
||||||
|
358→// 130.7→133.1 — the grid TRACKS it) in 1.9 s, 92% beat-gap
|
||||||
|
359→// regularity, confidence 0.99 through the body; techno/hip-hop
|
||||||
|
360→// patterns lock exactly; silence yields zero beats. Known limit:
|
||||||
|
361→// sparse kick-vs-snare backbeats (dnb) may lock half-tempo —
|
||||||
|
362→// stable, and two taps of Set Beat correct it. Visitor-stored
|
||||||
|
363→// beat configs migrate (floorLevel changed units, level → flux).
|
||||||
|
364→// The demo BPM slider is gone (a real track's tempo is detected,
|
||||||
|
365→// not dialed in) — replaced by a track-name chip in the header
|
||||||
|
366→// 7.00 — Zai-Magic: ten hand-authored presets in root/embedded/Zai-Magic
|
||||||
|
367→// built around BEAT-SYNCED COLOR — the color wheel turns on the
|
||||||
|
368→// kick, palettes swap per beat / per 4-beat bar, megabuf particles
|
||||||
|
369→// remember the hue of the beat that spawned them + new script
|
||||||
|
370→// tools: hsvr/hsvg/hsvb (HSV→RGB channels), smoothstep and the
|
||||||
|
371→// beatage kick-envelope variable — all documented in the help
|
||||||
|
372→// panel. (Numbered 7.00 rather than 6.10 — a new preset family
|
||||||
|
373→// plus new script-language features is a major release; 6.9.3 was
|
||||||
|
374→// the last patch of the 6.x line.) Beat engine fixes underneath so
|
||||||
|
375→// the Demo track actually
|
||||||
|
376→// locks tempo (was sporadic): demo synth remixed for detection
|
||||||
|
377→// (hot kick owning the 20–100 Hz band, off-beat bassline voiced
|
||||||
|
378→// C3+ with a sidechain pump — chronological envelope events!),
|
||||||
|
379→// and the detector's default threshold 1.4 → 1.2 (the 170 ms FFT
|
||||||
|
380→// window caps a clean kick's peak-to-mean at ~1.5×, so 1.4 fired
|
||||||
|
381→// only sporadically and the BPM never locked)
|
||||||
|
382→// 6.9.3 — canonical domain fix: metadataBase/og:url/og:image pointed at
|
||||||
|
383→// openvisualengine.com (a DEAD domain baked in by 6.9.1) while the
|
||||||
|
384→// real site is openvisengine.com — Facebook's "corrupted image"
|
||||||
|
385→// error was its crawler fetching the image from a host that serves
|
||||||
|
386→// nothing; all share URLs now use the correct short domain
|
||||||
|
387→// 6.9.2 — share-image fix: og:image re-encoded as BASELINE JPEG (the
|
||||||
|
388→// 6.9.1 progressive encoding rendered fine in every browser but
|
||||||
|
389→// Facebook's Sharing Debugger rejected it as "corrupted"), swapped
|
||||||
|
390→// in the title-card artwork (gradient wordmark over three
|
||||||
|
391→// visualizations) and added a ?v=2 cache-bust so crawlers fetch it
|
||||||
|
392→// 6.9.1 — social share preview: Open Graph + Twitter Card metadata with
|
||||||
|
393→// a 1200×630 screenshot of the app (public/og-image.jpg) — sharing
|
||||||
|
394→// openvisualengine.com on Facebook/X/WhatsApp/Discord now shows the
|
||||||
|
395→// big image card instead of a bare link
|
||||||
|
396→// 6.9 — house account: anonymous visitors play YouTube links through
|
||||||
|
397→// the operator's shared disposable-account cookie (server-side
|
||||||
|
398→// fallback under secrets/, never exposed) — YouTube bot walls stop
|
||||||
|
399→// applying to guests; visitor's own login always wins. Engine fix
|
||||||
|
400→// underneath: Deno runtime shipped in bin/ (YouTube requires a JS
|
||||||
|
401→// runtime for signature/n-challenge solving on authenticated
|
||||||
|
402→// sessions — without it, logged-in extractions return no formats)
|
||||||
|
403→// 6.8.2 — login friction cut to ~4 clicks: the exported cookies.txt /
|
||||||
|
404→// Cookie-Editor JSON file can be dragged straight onto the
|
||||||
|
405→// login box (or picked via “Import the exported file…”) — no
|
||||||
|
406→// opening the file, no copy/paste; “Log out” / sign-out
|
||||||
|
407→// revocation note added (the official Google OAuth flow can't
|
||||||
|
408→// power the site: Google's APIs never hand out stream URLs and
|
||||||
|
409→// restricted-scope verification would reject an audio player)
|
||||||
|
410→// 6.8.1 — cookie login made foolproof: the login dialog recognizes raw
|
||||||
|
411→// Cookie headers, cookies.txt exports AND Cookie-Editor JSON,
|
||||||
|
412→// validates the paste LIVE through one shared parser (a single
|
||||||
|
413→// cookie's value — like a bare LOGIN_INFO string — gets an honest
|
||||||
|
414→// "this will not log you in" warning), and the how-to steps lead
|
||||||
|
415→// with the one-click extension export for Chrome AND Firefox
|
||||||
|
416→// 6.8 — self-healing audio engine: when YouTube changes their mechanics
|
||||||
|
417→// the server detects the stale extractor, updates yt-dlp in the
|
||||||
|
418→// background (verified GitHub release download, atomic swap) and the
|
||||||
|
419→// player retries seamlessly behind an "Updating in Background"
|
||||||
|
420→// notice; bot walls answer "Google login required…" with an Open
|
||||||
|
421→// Settings action, and unfixable failures get the honest
|
||||||
|
422→// "A back-end problem has occurred. Please try again later."
|
||||||
|
423→// 6.7 — lazy playlist queue: a playlist link becomes a queue of URLs that
|
||||||
|
424→// streams ONE song at a time (each track is resolved by yt-dlp only
|
||||||
|
425→// right before it plays — next-track look-ahead at ≤15 s to go),
|
||||||
|
426→// queue bar with skip/jump/replay + server-side caps (max 2 parallel
|
||||||
|
427→// extractions); music-service framework: Spotify + Apple Music are
|
||||||
|
428→// registered providers whose playback ships in a later update
|
||||||
|
429→// (logins already save, links answer with an honest notice)
|
||||||
|
430→// 6.6 — music service logins (Settings tab): YouTube / YouTube Music,
|
||||||
|
431→// SoundCloud, Spotify, Apple Music cookie login, encrypted on the
|
||||||
|
432→// visitor's computer; Link button gains playlist picking + uses the
|
||||||
|
433→// login to bypass bot walls
|
||||||
|
434→// 6.5 — Settings tab (replaces the parked Native tab): theme picker with
|
||||||
|
435→// base chrome (Royal Purple / Matte Black / Black + Purple Outline /
|
||||||
|
436→// Light) × selectable highlight color (Purple, Yellow, Blue, Green,
|
||||||
|
437→// Red, Orange), all per-visitor; simple OVE logo as the site favicon
|
||||||
|
438→// 6.4 — beat detection suspends when no music is playing/loaded (no ghost
|
||||||
|
439→// beats after Stop, BPM resets) + dark theme restyle: royal purple
|
||||||
|
440→// chrome with light-yellow highlights
|
||||||
|
441→// 6.3 — light/dark theme tick switch in the header (white theme via .light
|
||||||
|
442→// palette remap; per-visitor, persisted by next-themes)
|
||||||
|
443→// 6.2 — security hardening: read-only embedded library (server refuses all
|
||||||
|
444→// writes) + per-visitor "user created" presets stored in the browser
|
||||||
|
445→// 6.1 — preset library quality pass: every embedded stand-in is now a UNIQUE
|
||||||
|
446→// generated visual (16 families × seeded parameters), approx badge
|
||||||
|
447→// 6.0 — renamed to "Open Visualization Engine" + footer version stamp
|
||||||
|
448→// 5.0 — folder-tree Presets page (Task 9–11 state, restored in Task 12)
|
||||||
|
449→// 4.0 — link audio source (yt-dlp resolve + stream proxy)
|
||||||
|
450→
|
||||||
|
451→export const APP_NAME = 'Open Visualization Engine'
|
||||||
|
452→export const APP_SHORT = 'OVE'
|
||||||
|
453→export const APP_VERSION = '7.14'
|
||||||
|
454→
|
||||||
454
tool-results/read_1793921019953_122085f9f51b.txt
Normal file
454
tool-results/read_1793921019953_122085f9f51b.txt
Normal file
|
|
@ -0,0 +1,454 @@
|
||||||
|
1→ 1→// OVE — app identity + version stamp.
|
||||||
|
2→ 2→//
|
||||||
|
3→ 3→// Single source of truth for the product name and version shown in the UI
|
||||||
|
4→ 4→// (header, footer, document title). Bump APP_VERSION whenever we ship a
|
||||||
|
5→ 5→// large change so the running build can always be identified at a glance —
|
||||||
|
6→ 6→// useful both for the z.ai Version History and for naming local backups
|
||||||
|
7→ 7→// (v6.0 = rename to Open Visualization Engine + version indicator).
|
||||||
|
8→ 8→//
|
||||||
|
9→ 9→// STANDING RULE (since v7.6): every APP_VERSION bump MUST come with a
|
||||||
|
10→ 10→// matching entry at the top of RELEASE_NOTES in src/lib/release-notes.ts —
|
||||||
|
11→ 11→// that list powers the header's "What's New!" dialog. A dev-mode guard
|
||||||
|
12→ 12→// warns in the console when the two drift apart.
|
||||||
|
13→ 13→//
|
||||||
|
14→ 14→// History:
|
||||||
|
15→ 15→// 7.14 — Arctic Rainbow re-imagined (user request): the old stand-in was
|
||||||
|
16→ 16→// plain white columns of rain dots sweeping upward. Rebuilt as a
|
||||||
|
17→ 17→// music-driven water spray, stateless projectile physics in the
|
||||||
|
18→ 18→// [point] program: each of up to 120 droplets is a deterministic
|
||||||
|
19→ 19→// parabola (own apex height 1.26..1.88, own flight time 1.9..3.4 s,
|
||||||
|
20→ 20→// phase-staggered clock; every splash cycle re-rolls its origin,
|
||||||
|
21→ 21→// drift and arch) -- launch from below the bottom edge, slow rise,
|
||||||
|
22→ 22→// hang, arch left or right, fall back off-screen. Drop count
|
||||||
|
23→ 23→// follows the music (vol_att/bass_att envelope, fast attack 6/s,
|
||||||
|
24→ 24→// slow release 1.2/s: silence = 0 drops, loud = all 120). Each
|
||||||
|
25→ 25→// drop owns a hue that shifts by its own per-drop amount on every
|
||||||
|
26→ 26→// detected beat, plus a subtle gradient along its tail. The tail
|
||||||
|
27→ 27→// is 12 explicit trail samples (0.014..0.44 s back along the arc)
|
||||||
|
28→ 28→// drawn as a tapered polyline in lines mode, gated by smoothstep
|
||||||
|
29→ 29→// on the fall speed: nothing while rising (crisp dot), fading in
|
||||||
|
30→ 30→// as the arch turns downward -- no persistence smear (persist
|
||||||
|
31→ 31→// 0.12). Slot map through cell centers (the v7.13.1 lesson), 14
|
||||||
|
32→ 32→// slots per drop (separator + head + 12 trail), n = 1680.
|
||||||
|
33→ 33→// EMBEDDED_SEED_VERSION 7.13.1 -> 7.14 re-seeds the library.
|
||||||
|
34→ 34→// 7.13.1 — Beat Matrix endpoint fix (user report): the rightmost column
|
||||||
|
35→ 35→// of dots sat one row short -- its bottom dot missing -- while a
|
||||||
|
36→ 36→// phantom dot spilled one column past the wall's right edge at
|
||||||
|
37→ 37→// the top. Root cause: the engine's point position i spans 0..1
|
||||||
|
38→ 38→// INCLUSIVE (classic AVS Superscope convention, engine.ts: i =
|
||||||
|
39→ 39→// k/(n-1)), but Beat Matrix's grid math assumed exclusive i = k/n:
|
||||||
|
40→ 40→// floor(i*cols) turned the last point into col 16 (one past the
|
||||||
|
41→ 41→// 16-column wall) at row 0, and column 15 was left with nine
|
||||||
|
42→ 42→// dots (rows 0-8, no bottom). Fix: the script now maps through
|
||||||
|
43→ 43→// cell centers -- cell = i*(cols*rows-1)+0.5, col =
|
||||||
|
44→ 44→// floor(cell/rows), row = floor(cell-col*rows) -- which puts
|
||||||
|
45→ 45→// every floor() half a cell away from an integer boundary, so
|
||||||
|
46→ 46→// the endpoint can never spill and float dust can never shuffle
|
||||||
|
47→ 47→// a dot. Full 16x10 bijection verified headlessly (all 160
|
||||||
|
48→ 48→// points in 160 distinct cells, col<=15, row<=9, bottom-right
|
||||||
|
49→ 49→// cell present). Sweep of the other 100 presets: every other
|
||||||
|
50→ 50→// floor(i*M) use is benign -- angular presets close their loop
|
||||||
|
51→ 51→// (bar=bars lands exactly on bar 0's angle), Aurora Veil's
|
||||||
|
52→ 52→// phantom lands off-screen (x=1.19), the megabuf particle
|
||||||
|
53→ 53→// presets draw their phantom slot at alpha 0, and the converted
|
||||||
|
54→ 54→// Winamp classics rendered identically under original AVS
|
||||||
|
55→ 55→// (which also uses inclusive i). EMBEDDED_SEED_VERSION 7.12 ->
|
||||||
|
56→ 56→// 7.13.1 re-seeds the library so every visitor gets the fixed
|
||||||
|
57→ 57→// Beat Matrix.
|
||||||
|
58→ 58→// 7.13 — one build, every feature: the parallel development lines are
|
||||||
|
59→ 59→// merged. This tree now carries BOTH the community OVE-Directory
|
||||||
|
60→ 60→// (v7.11 here) AND the parallel line's additions — the spectrum
|
||||||
|
61→ 61→// 16 kHz cap (its 7.10.1), the screen wake lock + respectful
|
||||||
|
62→ 62→// Demo button + one-click Save (its 7.11, folded into this
|
||||||
|
63→ 63→// history as 7.10.2 to keep one clean timeline), and the
|
||||||
|
64→ 64→// AVS-style background movement family (bgrot / bgx / bgy /
|
||||||
|
65→ 65→// bgzoom) with the eleventh Zai-Magic preset "osc-rotations"
|
||||||
|
66→ 66→// (its 7.12). Git three-way merge against the shared v7.10
|
||||||
|
67→ 67→// ancestor (identical tree hashes verified on both sides); the
|
||||||
|
68→ 68→// only hand-merged surface was the Script tab toolbar, which
|
||||||
|
69→ 69→// now carries the one-click Save and "Upload to OVE-Directory"
|
||||||
|
70→ 70→// side by side, and the two changelogs (here + release-notes).
|
||||||
|
71→ 71→// 7.12 — background movement + osc-rotations. Scripts can now move the
|
||||||
|
72→ 72→// whole framebuffer every frame, the way the classic Winamp AVS
|
||||||
|
73→ 73→// plugins did: bgrot (radians/frame, + clockwise on screen),
|
||||||
|
74→ 74→// bgx / bgy (per-frame drift in the -1..1 screen convention, y
|
||||||
|
75→ 75→// up), bgzoom (< 1 draw-in, > 1 blow-out) -- read after [frame]/
|
||||||
|
76→ 76→// [beat] so this frame's values apply this frame. The engine
|
||||||
|
77→ 77→// snapshots the canvas into an offscreen buffer, wipes, and
|
||||||
|
78→ 78→// draws the snapshot back through the transform, so the
|
||||||
|
79→ 79→// persistence fade blends the moving trails: "speed accelerates
|
||||||
|
80→ 80→// to the beat, a hard hit flips the spin, fade to black stays
|
||||||
|
81→ 81→// slow" is pure script (a beatage envelope drives the rate; a
|
||||||
|
82→ 82→// [beat] section flips a dir variable on hard hits via bass vs
|
||||||
|
83→ 83→// bass_att). Neutral readings (0/0/0/1) skip the pass entirely
|
||||||
|
84→ 84→// (zero cost for every preset that doesn't move); non-finite
|
||||||
|
85→ 85→// values fall back to neutral (a NaN in a canvas transform
|
||||||
|
86→ 86→// matrix would black the screen with no error); per-frame
|
||||||
|
87→ 87→// clamps: rot ±0.6 rad, drift ±0.25, zoom 0.5..2. New 11th
|
||||||
|
88→ 88→// Zai-Magic preset "osc-rotations" (user-requested): a
|
||||||
|
89→ 89→// stationary centered oscilloscope (time-domain v) whose color
|
||||||
|
90→ 90→// is mirror-symmetric -- points equidistant left/right of
|
||||||
|
91→ 91→// center shift together, the center points hold their own hue
|
||||||
|
92→ 92→// -- over a background rotating cw/ccw to the music that
|
||||||
|
93→ 93→// temporarily accelerates on the beat; hard hits
|
||||||
|
94→ 94→// (bass > 1.25 × bass_att) flip the spin; a whisper of draw-in
|
||||||
|
95→ 95→// (bgzoom ~0.999) spirals the trails inward; persist 0.965
|
||||||
|
96→ 96→// keeps the fade slow so colors blend. Help panel: bg* variable
|
||||||
|
97→ 97→// rows + three new example formulas. EMBEDDED_SEED_VERSION
|
||||||
|
98→ 98→// 7.10 -> 7.12 (library re-seed adds the preset; the share
|
||||||
|
99→ 99→// codec's dictionary stays frozen per its compatibility
|
||||||
|
100→ 100→// contract -- bg* identifiers ride the pair packing like any
|
||||||
|
101→ 101→// custom variable). Headless validation of the preset's
|
||||||
|
102→ 102→// compiled programs: quiet 0.004 rad/frame, hard beat 0.026
|
||||||
|
103→ 103→// (6.5×) + direction flip, soft beat no flip, mirrored points
|
||||||
|
104→ 104→// share hue / center distinct
|
||||||
|
105→ 105→// 7.11 — the community OVE-Directory. A sixth tab (Directory) lists every
|
||||||
|
106→ 106→// script published to the community directory — a separate VPS at
|
||||||
|
107→ 107→// s3374dir.openvisengine.com, reached ONLY through this server's
|
||||||
|
108→ 108→// backend proxy (/api/ove-directory/*), which holds the API key in
|
||||||
|
109→ 109→// env and re-validates every upload (the share string must decode
|
||||||
|
110→ 110→// AND compile). Publishing: the Script tab gains "Upload to
|
||||||
|
111→ 111→// OVE-Directory" (enabled only while the editor shows ✔ Compiled OK
|
||||||
|
112→ 112→// with no runtime error); the script travels as its Script⇢Hash
|
||||||
|
113→ 113→// string, so the directory stores a fraction of the source. The
|
||||||
|
114→ 114→// listing carries each entry's name, type and date, a 1–5 star
|
||||||
|
115→ 115→// widget (the voter id is an HttpOnly cookie minted by the proxy;
|
||||||
|
116→ 116→// one vote per script per visitor, enforced upstream), the live
|
||||||
|
117→ 117→// "Average: #.#" to the right of the stars, and sorting by Name /
|
||||||
|
118→ 118→// Type / Date / Rating (done upstream, both directions). "Get"
|
||||||
|
119→ 119→// fetches the full record, decodes the hash back into the exact
|
||||||
|
120→ 120→// script, files it in this browser's preset tree under
|
||||||
|
121→ 121→// root/user created/OVE-Directory (content-deduped, names numbered
|
||||||
|
122→ 122→// "(1) (2) (3)…" on collision) and offers "Open in editor".
|
||||||
|
123→ 123→// Duplicate uploads resolve as success (409 DUPLICATE_HASH carries
|
||||||
|
124→ 124→// the existing row); duplicate votes show the stored aggregates.
|
||||||
|
125→ 125→// 7.10.2 — wake lock, a respectful Demo button, one-click save. While any
|
||||||
|
126→ 126→// audio source is live the site holds a Screen Wake Lock so phone
|
||||||
|
127→ 127→// and tablet screens stay on (released the moment the engine goes
|
||||||
|
128→ 128→// idle so the normal inactivity dimming returns; browsers silently
|
||||||
|
129→ 129→// drop the sentinel on hide, so a visibilitychange watcher
|
||||||
|
130→ 130→// re-acquires it on return while music is still going;
|
||||||
|
131→ 131→// unsupported browsers degrade silently). The Demo Track button no
|
||||||
|
132→ 132→// longer force-engages the Sequence checkbox: a visitor who loaded
|
||||||
|
133→ 133→// a preset (tree double-click / Load / Copy to my presets / created
|
||||||
|
134→ 134→// one), brought their own script material into the editor (typed,
|
||||||
|
135→ 135→// pasted, Hash⇢Script, file), saved their own default startup
|
||||||
|
136→ 136→// script, or runs their own Random/Sequence mix keeps their
|
||||||
|
137→ 137→// selection -- Demo just plays the track (the showcase's tree
|
||||||
|
138→ 138→// re-scope, 20 s interval and retire-on-end all skip too; a new
|
||||||
|
139→ 139→// per-visit manualChoice flag + a boot-script snapshot decide, and
|
||||||
|
140→ 140→// ensureDefaultPreset gained the same guard so mic/file/link never
|
||||||
|
141→ 141→// clobber a custom draft either; the showcase now only starts for
|
||||||
|
142→ 142→// a visit that never picked or typed anything). Script tab: a valid
|
||||||
|
143→ 143→// script shows the Save button with NO save target needed -- one
|
||||||
|
144→ 144→// click creates a preset under "user created", auto-named from the
|
||||||
|
145→ 145→// script's name: header (a hash import of "Spectrum Bars:[▼…]"
|
||||||
|
146→ 146→// saves as "Spectrum Bars"), "Empty Name" when nameless, " (1)"/
|
||||||
|
147→ 147→// " (2)"… suffixes on conflicts, and whenever the name had to be
|
||||||
|
148→ 148→// invented a "Give This Preset a Name" dialog opens to rename the
|
||||||
|
149→ 149→// just-saved preset in one step (the preset is always saved FIRST,
|
||||||
|
150→ 150→// renamed second, so nothing is ever lost to a naming problem).
|
||||||
|
151→ 151→// Hash⇢Script imports clear the stale save target (the editor now
|
||||||
|
152→ 152→// holds foreign content) and pass the decoded label through as the
|
||||||
|
153→ 153→// save-name fallback when the script has no name: header
|
||||||
|
154→ 154→// 7.10.1 — the frozen right edge of spectrum visuals. getspec()/sv mapped
|
||||||
|
155→ 155→// the bar field across the whole FFT (bin 2 ... Nyquist, ~12 Hz to
|
||||||
|
156→ 156→// 24 kHz), but music carries almost nothing above ~16 kHz and every
|
||||||
|
157→ 157→// lossy source low-passes even lower (192 kbps MP3 at ~18.6 kHz,
|
||||||
|
158→ 158→// YouTube AAC at ~15.5 kHz; measured across the demo collection:
|
||||||
|
159→ 159→// 0.000% of total magnitude above 18.6 kHz), so the rightmost ~20%
|
||||||
|
160→ 160→// of every spectrum visual read a permanent zero -- bars that never
|
||||||
|
161→ 161→// moved. getSpectrum() now tops the field out at 16 kHz
|
||||||
|
162→ 162→// (SPEC_TOP_HZ, matching the spectral-flux cap in getBands()), so
|
||||||
|
163→ 163→// the right edge lands on the 12-16 kHz "air" band where cymbals,
|
||||||
|
164→ 164→// hats and synth sparkle live. rust-agent's spectrum_at() mirrors
|
||||||
|
165→ 165→// the change, and the help panel's sv entry now states the span.
|
||||||
|
166→ 166→// 7.10 — ten demo tracks + first-person changelog + declutter. The demo
|
||||||
|
167→ 167→// source is now a COLLECTION: ten mastered tracks in
|
||||||
|
168→ 168→// public/demo-tracks/ (demo-01.mp3 … demo-10.mp3, filename order
|
||||||
|
169→ 169→// 1→10, replacing the single Neural Mecha Pulse file). Each Demo
|
||||||
|
170→ 170→// click plays the visitor's PLACE in the collection once through;
|
||||||
|
171→ 171→// the place advances when a pass truly ends — natural finish or
|
||||||
|
172→ 172→// the Stop button ('ended'/'stop' teardown reasons, a new
|
||||||
|
173→ 173→// onDemoEnded signature) — and wraps 10→1; switching to another
|
||||||
|
174→ 174→// source keeps the place. The place persists per visitor in
|
||||||
|
175→ 175→// localStorage (ove.demoTrack.next), so returning picks up where
|
||||||
|
176→ 176→// they left off. The engine caches one decoded buffer at a time,
|
||||||
|
177→ 177→// keyed by track index (~50–60 MB PCM each — all ten would be half
|
||||||
|
178→ 178→// a gigabyte). The header chip + footer SRC label show the current
|
||||||
|
179→ 179→// track's name (Demo Track N). RELEASE_NOTES rewritten in the
|
||||||
|
180→ 180→// operator's first-person voice (no more third-person marketing
|
||||||
|
181→ 181→// copy or addressing the visitor), and every em dash in the
|
||||||
|
182→ 182→// interface replaced with a plain double minus ( -- ) — including
|
||||||
|
183→ 183→// the embedded preset library's descriptions and script comments
|
||||||
|
184→ 184→// (EMBEDDED_SEED_VERSION 7.01 → 7.10 re-seed). Declutter: the
|
||||||
|
185→ 185→// landing menu's source list line, the presets panel's two
|
||||||
|
186→ 186→// read-only paragraphs, the Random/Sequence hint line, the empty
|
||||||
|
187→ 187→// user-folder note reworded, the Settings theme/highlight/Native
|
||||||
|
188→ 188→// paragraphs, the link dialog's privacy footnote and the footer's
|
||||||
|
189→ 189→// engine-ok dot are all gone — compile errors still toast, so the
|
||||||
|
190→ 190→// always-green badge was noise
|
||||||
|
191→ 191→// 7.9 — footer credit + What's New polish. The footer's blank middle now
|
||||||
|
192→ 192→// carries the maker's signature: "© {year} · Site Created &
|
||||||
|
193→ 193→// Deployed by Sum'ting White" (year generated from the clock,
|
||||||
|
194→ 194→// hydration-guarded) with a "View my portfolio at zcreations.dev"
|
||||||
|
195→ 195→// link that opens zcreations.dev in a new tab — centered in the
|
||||||
|
196→ 196→// blank area from xl (mx-auto between the stats and the version
|
||||||
|
197→ 197→// stamp), dropping to its own centered row on narrower screens so
|
||||||
|
198→ 198→// nothing ever crowds. The What's New dialog got a typographic
|
||||||
|
199→ 199→// tidy-up: a hairline divider above every release box
|
||||||
|
200→ 200→// (border-zinc-800 — a ramp step, so it matches every base theme
|
||||||
|
201→ 201→// through the globals.css remaps), and each change row became an
|
||||||
|
202→ 202→// invisible two-column grid — the kind tag top-right justified
|
||||||
|
203→ 203→// against the column boundary, the description top-left with word
|
||||||
|
204→ 204→// wrap — so all descriptions start on the same pixel column
|
||||||
|
205→ 205→// 7.8 — share strings 2.0: ~72% shorter than the script (was ~49%). The
|
||||||
|
206→ 206→// base-62 idea, cranked to 11,172… 32,164: after the symbol
|
||||||
|
207→ 207→// dictionary (which grew ~170 data-mined entries — every function
|
||||||
|
208→ 208→// WITH its open paren, every engine variable, the AVS idioms
|
||||||
|
209→ 209→// "hue = hue + dt * " and "t = t + dt * ", " * 0.", ") * ",
|
||||||
|
210→ 210→// blank-line+section skeletons, "linewidth: " / "blend: additive"
|
||||||
|
211→ 211→// headers, ~70 number literals, and the library's common
|
||||||
|
212→ 212→// variable names incl. chroma) the whole body is read as ONE BIG
|
||||||
|
213→ 213→// NUMBER and rewritten in base 32,164 — one digit per character
|
||||||
|
214→ 214→// from the CJK + Hangul blocks, ~15 bits a character vs base-62's
|
||||||
|
215→ 215→// 5.95 (so "1400" costs ~0.85 of a character). The decoder gets
|
||||||
|
216→ 216→// the body's alphabet back from a ~32-character bitmap header
|
||||||
|
217→ 217→// instead of ~80 literal symbols. Short scripts where the header
|
||||||
|
218→ 218→// wouldn't pay keep the v7.7 symbol form (encoder picks whichever
|
||||||
|
219→ 219→// is shorter); ▲ strings from v7.7 still decode; still lossless
|
||||||
|
220→ 220→// for ANY text, still copy-safe, still self-checking. Corpus
|
||||||
|
221→ 221→// measure: median share string is 27% of the script's length
|
||||||
|
222→ 222→// (best 22%), e.g. Bass Bloom 1341 → 326 characters
|
||||||
|
223→ 223→// 7.7 — script sharing + settings declutter. Script⇢Hash / Hash⇢Script:
|
||||||
|
224→ 224→// two buttons in the Script tab turn the editor's script into ONE
|
||||||
|
225→ 225→// short shareable Unicode string ("Bass Bloom:[▲…①①①①①①①①]") and
|
||||||
|
226→ 226→// paste one back — the script returns exactly as written (lossless
|
||||||
|
227→ 227→// for ANY text, compiling NOT required, so WIP scripts can be
|
||||||
|
228→ 228→// shared for debugging). The string is built from built-in symbol
|
||||||
|
229→ 229→// references (♤ = line return, ♡ = beats, ♠ = beatage, ♦ = space,
|
||||||
|
230→ 230→// ♩♪♫♬ = the four sections, one symbol per function/variable, plus
|
||||||
|
231→ 231→// a pair-packing layer that folds leftover identifier/number text
|
||||||
|
232→ 232→// two chars per CJK code point) — ~50% shorter than the script on
|
||||||
|
233→ 233→// real presets. Copy-safe by construction (no raw whitespace or
|
||||||
|
234→ 234→// control chars; platform-injected variation selectors / zero-
|
||||||
|
235→ 235→// widths stripped on paste) and self-checking: 8 check digits (an
|
||||||
|
236→ 236→// FNV-1a digest rendered as ①–⑯) reject strings that were cut off
|
||||||
|
237→ 237→// by a character limit or altered in transit, instead of decoding
|
||||||
|
238→ 238→// into garbage. Also: the Music Services log in / "log out of all"
|
||||||
|
239→ 239→// section in Settings is parked (MUSIC_SERVICES_UI flag) — the
|
||||||
|
240→ 240→// built-in house account already powers Stream-a-Link for
|
||||||
|
241→ 241→// everyone; the section returns reworked in a later update
|
||||||
|
242→ 242→// 7.6.1 — header declutter: the Set Beat button is gone from the header
|
||||||
|
243→ 243→// (tap-tempo lives in the Beat tab, and the Space shortcut works
|
||||||
|
244→ 244→// anywhere — both unchanged). The beat indicator dot stays.
|
||||||
|
245→ 245→// 7.6 — "What's New!" dialog: a Sparkles button in the header (left of
|
||||||
|
246→ 246→// the theme switch) opens the visitor-facing changelog — every
|
||||||
|
247→ 247→// release from v4.0 on, newest first, each with one-line Added /
|
||||||
|
248→ 248→// Improved / Fixed / Removed notes (src/lib/release-notes.ts,
|
||||||
|
249→ 249→// color-coded tags, the running version badged "current",
|
||||||
|
250→ 250→// scrollable with the thin scrollbar). A small emerald dot on the
|
||||||
|
251→ 251→// button marks updates the visitor hasn't seen yet — per visitor
|
||||||
|
252→ 252→// in localStorage, cleared on open (mount-guarded, no hydration
|
||||||
|
253→ 253→// mismatch). Standing rule added: version bumps ship with their
|
||||||
|
254→ 254→// release note; a dev-mode guard warns when they drift
|
||||||
|
255→ 255→// 7.5.1 — Link dialog width fix: text no longer escapes the dialog box.
|
||||||
|
256→ 256→// Root cause: Radix dialogs lay their children in an implicit CSS
|
||||||
|
257→ 257→// grid whose single column sizes to the widest child's min-content;
|
||||||
|
258→ 258→// the RECENT LINKS rows carry truncate spans (white-space: nowrap),
|
||||||
|
259→ 259→// and Chromium measures a nowrap flex item's full text width for
|
||||||
|
260→ 260→// that measurement — so a long saved title (e.g. "Bobby Foxx — This
|
||||||
|
261→ 261→// Is Why We Can't Have Nice Things (Official Music Video)") blew
|
||||||
|
262→ 262→// the column ~40 px past the 448 px dialog, and EVERY child (title,
|
||||||
|
263→ 263→// description, input + Load, rows, footer) laid out beyond the
|
||||||
|
264→ 264→// right border. Fix: the shared Dialog/AlertDialog content now pins
|
||||||
|
265→ 265→// the column to minmax(0,1fr) — capped at the available width, so
|
||||||
|
266→ 266→// no nowrap content can ever stretch a dialog again (covers every
|
||||||
|
267→ 267→// dialog app-wide) — the Link dialog widened to max-w-lg for
|
||||||
|
268→ 268→// comfier wrapping, and min-w-0 + break-words added on the
|
||||||
|
269→ 269→// recents/playlist containers and error text as belt-and-braces
|
||||||
|
270→ 270→// 7.5 — yt-dlp compatibility: single tracks resolve by DOWNLOAD + CONVERT
|
||||||
|
271→ 271→// instead of proxying the extractor's stream URL. The old stream
|
||||||
|
272→ 272→// URLs were IP-locked (googlevideo bakes in the resolver's IP),
|
||||||
|
273→ 273→// expired within hours, and arrived as webm/opus — a container
|
||||||
|
274→ 274→// Safari & co. refuse ("The media resource … was not suitable").
|
||||||
|
275→ 275→// The server now runs the operator's pipeline per track:
|
||||||
|
276→ 276→// yt-dlp -f bestaudio/best -x --audio-format mp3
|
||||||
|
277→ 277→// --audio-quality 0 -o "<dir>/%(title)s.%(ext)s" URL
|
||||||
|
278→ 278→// — audio-only download, universal MP3 at VBR-0 (~245 kbps), the
|
||||||
|
279→ 279→// video's real title preserved for the Playing: label. The
|
||||||
|
280→ 280→// finished file streams same-origin with full Range support from
|
||||||
|
281→ 281→// a per-track workspace dir (30-min sliding TTL + orphan sweeper);
|
||||||
|
282→ 282→// /api/audio/stream gained a file-serving mode. Hardening around
|
||||||
|
283→ 283→// it: shared failure classifier for both yt-dlp passes (metadata +
|
||||||
|
284→ 284→// download), 180 s download budget, 300 MB cap, bot-wall/stale-
|
||||||
|
285→ 285→// extractor answers unchanged, ffmpeg-missing degrades to the old
|
||||||
|
286→ 286→// proxy path (logged once), queue look-ahead lead 15 s → 60 s so
|
||||||
|
287→ 287→// playlist transitions don't stall behind conversions
|
||||||
|
288→ 288→// 7.4 — Sequence mode: a second mix checkbox next to Random. Ticked, it
|
||||||
|
289→ 289→// cycles the scope folder's presets IN ORDER (the tree's exact
|
||||||
|
290→ 290→// top-to-bottom order) instead of shuffling — and when it moves
|
||||||
|
291→ 291→// past the last preset it wraps back to the first, so the folder
|
||||||
|
292→ 292→// loops forever. Random and Sequence are mutually exclusive
|
||||||
|
293→ 293→// (ticking either one unticks the other; both share the same
|
||||||
|
294→ 294→// delay and folder scope), Random now never loads the same preset
|
||||||
|
295→ 295→// twice in a row, and Sequence holds its breath through silence
|
||||||
|
296→ 296→// exactly like Random does. The Demo showcase now runs SEQUENCE —
|
||||||
|
297→ 297→// the one play-through walks every Zai-Magic preset in order
|
||||||
|
298→ 298→// (20 s each) rather than repeating random picks, and when the
|
||||||
|
299→ 299→// demo stops, whichever mode it checked is unchecked again
|
||||||
|
300→ 300→// 7.3 — demo-flow refinements: the Demo Track is a single pass — it plays
|
||||||
|
301→ 301→// "Neural Mecha Pulse" once through and stops (a demo, not a radio
|
||||||
|
302→ 302→// station). However it ends (natural finish, Stop, or switching to
|
||||||
|
303→ 303→// another source) the showcase unwinds: the randomizer the Demo
|
||||||
|
304→ 304→// flow force-checked is unchecked again, and the visitor lands
|
||||||
|
305→ 305→// back on the "Select an audio source" overlay. Preset-change
|
||||||
|
306→ 306→// popups are gone ("Preset loaded" / "Random preset" / "Default
|
||||||
|
307→ 307→// preset running") — the footer's SCRIPT: label already echoes
|
||||||
|
308→ 308→// every change, so the toasts were pure intrusion; only compile
|
||||||
|
309→ 309→// errors still toast. Club-mode randomizer now respects silence:
|
||||||
|
310→ 310→// when no music is playing it never re-shuffles the selected
|
||||||
|
311→ 311→// preset, even with the checkbox on — it waits for the next track
|
||||||
|
312→ 312→// 7.2.1 — Supernova (Zai-Magic) preset fix: the particle kill condition
|
||||||
|
313→ 313→// (|px|+|py| > 2.4) was physically unreachable — drag caps a
|
||||||
|
314→ 314→// particle's total travel at vel/1.6 ≈ 1.0, so nothing ever
|
||||||
|
315→ 315→// crossed 1.4, nothing ever died, and since respawning requires a
|
||||||
|
316→ 316→// dead particle, the per-beat "budget = n" detonation could never
|
||||||
|
317→ 317→// fire after the initial one — the preset exploded once on load,
|
||||||
|
318→ 318→// then faded to permanent black. Now every beat force-clears the
|
||||||
|
319→ 319→// whole 620-particle field in [beat] (guaranteed full re-detonation
|
||||||
|
320→ 320→// on the very same frame), fully-cooled particles die by fade so
|
||||||
|
321→ 321→// long beat gaps leave a gentle ember drizzle instead of black,
|
||||||
|
322→ 322→// and each blast re-randomizes its geometry (no two explosions
|
||||||
|
323→ 323→// alike). Embedded library re-seeded (seed marker 7.01)
|
||||||
|
324→ 324→// 7.2 — new-visitor showcase flow. Defaults for first-time visitors:
|
||||||
|
325→ 325→// the Black + Purple Outline theme with the Blue highlight
|
||||||
|
326→ 326→// (previously royal purple + yellow), and Zai-Magic "Bass Bloom"
|
||||||
|
327→ 327→// as the startup preset (previously the first Synth-C classic).
|
||||||
|
328→ 328→// The idle overlay's Demo Track button now carries an animated
|
||||||
|
329→ 329→// reflective shimmer (skewed light band sweeping left→right every
|
||||||
|
330→ 330→// 3.2 s, reduced-motion safe) to draw the click — and that one
|
||||||
|
331→ 331→// click starts the full showcase: the demo track plays while the
|
||||||
|
332→ 332→// preset tree selects + expands Zai-Magic, randomize switches on
|
||||||
|
333→ 333→// scoped to that folder with a 20-second change interval. Music
|
||||||
|
334→ 334→// started with NO preset active (own draft running / selection
|
||||||
|
335→ 335→// cleared) auto-loads Bass Bloom. Preset changes — manual load,
|
||||||
|
336→ 336→// the random mix, the Demo flow — no longer hard-cut: the engine
|
||||||
|
337→ 337→// snapshots the outgoing frame and blur-dissolves it into the new
|
||||||
|
338→ 338→// preset over 700 ms (progressive blur + easeOutCubic fade + a
|
||||||
|
339→ 339→// slight scale-up so the softening edges never gap; plain
|
||||||
|
340→ 340→// crossfade where ctx.filter is unsupported)
|
||||||
|
341→ 341→// 7.1 — real demo track + a beat detector that survives real music.
|
||||||
|
342→ 342→// The Demo button now plays "Neural Mecha Pulse" (the operator's
|
||||||
|
343→ 343→// mastered industrial-EDM banger, public/demo-track.mp3, loops
|
||||||
|
344→ 344→// gaplessly) instead of the synthetic techno pattern — a real
|
||||||
|
345→ 345→// recording is the honest test: deep sub for the bass bands,
|
||||||
|
346→ 346→// driving mids, crisp highs, and a kick buried in sustained
|
||||||
|
347→ 347→// sub-bass. That last part exposed the 6.x detector as fundamentally
|
||||||
|
348→ 348→// broken on mastered music: a LEVEL threshold can never see the
|
||||||
|
349→ 349→// kick through the sub (peak-to-mean caps at ~1.2× → sporadic
|
||||||
|
350→ 350→// fires, phantom 120-BPM locks from the minInterval floor).
|
||||||
|
351→ 351→// v7.1 rewrites detection the way real beat trackers work
|
||||||
|
352→ 352→// (aubio/librosa-style): a full-band spectral-flux ONSET envelope
|
||||||
|
353→ 353→// → autocorrelation over 300–1000 ms lags with a log-BPM prior →
|
||||||
|
354→ 354→// comb-filter phase with grid hysteresis → beats fired on an
|
||||||
|
355→ 355→// absolute grid kept glued by a PLL pull. Headlessly validated
|
||||||
|
356→ 356→// against the actual track PCM (AnalyserNode chain replicated
|
||||||
|
357→ 357→// frame-for-frame): locks the true ~133 BPM (the song drifts
|
||||||
|
358→ 358→// 130.7→133.1 — the grid TRACKS it) in 1.9 s, 92% beat-gap
|
||||||
|
359→ 359→// regularity, confidence 0.99 through the body; techno/hip-hop
|
||||||
|
360→ 360→// patterns lock exactly; silence yields zero beats. Known limit:
|
||||||
|
361→ 361→// sparse kick-vs-snare backbeats (dnb) may lock half-tempo —
|
||||||
|
362→ 362→// stable, and two taps of Set Beat correct it. Visitor-stored
|
||||||
|
363→ 363→// beat configs migrate (floorLevel changed units, level → flux).
|
||||||
|
364→ 364→// The demo BPM slider is gone (a real track's tempo is detected,
|
||||||
|
365→ 365→// not dialed in) — replaced by a track-name chip in the header
|
||||||
|
366→ 366→// 7.00 — Zai-Magic: ten hand-authored presets in root/embedded/Zai-Magic
|
||||||
|
367→ 367→// built around BEAT-SYNCED COLOR — the color wheel turns on the
|
||||||
|
368→ 368→// kick, palettes swap per beat / per 4-beat bar, megabuf particles
|
||||||
|
369→ 369→// remember the hue of the beat that spawned them + new script
|
||||||
|
370→ 370→// tools: hsvr/hsvg/hsvb (HSV→RGB channels), smoothstep and the
|
||||||
|
371→ 371→// beatage kick-envelope variable — all documented in the help
|
||||||
|
372→ 372→// panel. (Numbered 7.00 rather than 6.10 — a new preset family
|
||||||
|
373→ 373→// plus new script-language features is a major release; 6.9.3 was
|
||||||
|
374→ 374→// the last patch of the 6.x line.) Beat engine fixes underneath so
|
||||||
|
375→ 375→// the Demo track actually
|
||||||
|
376→ 376→// locks tempo (was sporadic): demo synth remixed for detection
|
||||||
|
377→ 377→// (hot kick owning the 20–100 Hz band, off-beat bassline voiced
|
||||||
|
378→ 378→// C3+ with a sidechain pump — chronological envelope events!),
|
||||||
|
379→ 379→// and the detector's default threshold 1.4 → 1.2 (the 170 ms FFT
|
||||||
|
380→ 380→// window caps a clean kick's peak-to-mean at ~1.5×, so 1.4 fired
|
||||||
|
381→ 381→// only sporadically and the BPM never locked)
|
||||||
|
382→ 382→// 6.9.3 — canonical domain fix: metadataBase/og:url/og:image pointed at
|
||||||
|
383→ 383→// openvisualengine.com (a DEAD domain baked in by 6.9.1) while the
|
||||||
|
384→ 384→// real site is openvisengine.com — Facebook's "corrupted image"
|
||||||
|
385→ 385→// error was its crawler fetching the image from a host that serves
|
||||||
|
386→ 386→// nothing; all share URLs now use the correct short domain
|
||||||
|
387→ 387→// 6.9.2 — share-image fix: og:image re-encoded as BASELINE JPEG (the
|
||||||
|
388→ 388→// 6.9.1 progressive encoding rendered fine in every browser but
|
||||||
|
389→ 389→// Facebook's Sharing Debugger rejected it as "corrupted"), swapped
|
||||||
|
390→ 390→// in the title-card artwork (gradient wordmark over three
|
||||||
|
391→ 391→// visualizations) and added a ?v=2 cache-bust so crawlers fetch it
|
||||||
|
392→ 392→// 6.9.1 — social share preview: Open Graph + Twitter Card metadata with
|
||||||
|
393→ 393→// a 1200×630 screenshot of the app (public/og-image.jpg) — sharing
|
||||||
|
394→ 394→// openvisualengine.com on Facebook/X/WhatsApp/Discord now shows the
|
||||||
|
395→ 395→// big image card instead of a bare link
|
||||||
|
396→ 396→// 6.9 — house account: anonymous visitors play YouTube links through
|
||||||
|
397→ 397→// the operator's shared disposable-account cookie (server-side
|
||||||
|
398→ 398→// fallback under secrets/, never exposed) — YouTube bot walls stop
|
||||||
|
399→ 399→// applying to guests; visitor's own login always wins. Engine fix
|
||||||
|
400→ 400→// underneath: Deno runtime shipped in bin/ (YouTube requires a JS
|
||||||
|
401→ 401→// runtime for signature/n-challenge solving on authenticated
|
||||||
|
402→ 402→// sessions — without it, logged-in extractions return no formats)
|
||||||
|
403→ 403→// 6.8.2 — login friction cut to ~4 clicks: the exported cookies.txt /
|
||||||
|
404→ 404→// Cookie-Editor JSON file can be dragged straight onto the
|
||||||
|
405→ 405→// login box (or picked via “Import the exported file…”) — no
|
||||||
|
406→ 406→// opening the file, no copy/paste; “Log out” / sign-out
|
||||||
|
407→ 407→// revocation note added (the official Google OAuth flow can't
|
||||||
|
408→ 408→// power the site: Google's APIs never hand out stream URLs and
|
||||||
|
409→ 409→// restricted-scope verification would reject an audio player)
|
||||||
|
410→ 410→// 6.8.1 — cookie login made foolproof: the login dialog recognizes raw
|
||||||
|
411→ 411→// Cookie headers, cookies.txt exports AND Cookie-Editor JSON,
|
||||||
|
412→ 412→// validates the paste LIVE through one shared parser (a single
|
||||||
|
413→ 413→// cookie's value — like a bare LOGIN_INFO string — gets an honest
|
||||||
|
414→ 414→// "this will not log you in" warning), and the how-to steps lead
|
||||||
|
415→ 415→// with the one-click extension export for Chrome AND Firefox
|
||||||
|
416→ 416→// 6.8 — self-healing audio engine: when YouTube changes their mechanics
|
||||||
|
417→ 417→// the server detects the stale extractor, updates yt-dlp in the
|
||||||
|
418→ 418→// background (verified GitHub release download, atomic swap) and the
|
||||||
|
419→ 419→// player retries seamlessly behind an "Updating in Background"
|
||||||
|
420→ 420→// notice; bot walls answer "Google login required…" with an Open
|
||||||
|
421→ 421→// Settings action, and unfixable failures get the honest
|
||||||
|
422→ 422→// "A back-end problem has occurred. Please try again later."
|
||||||
|
423→ 423→// 6.7 — lazy playlist queue: a playlist link becomes a queue of URLs that
|
||||||
|
424→ 424→// streams ONE song at a time (each track is resolved by yt-dlp only
|
||||||
|
425→ 425→// right before it plays — next-track look-ahead at ≤15 s to go),
|
||||||
|
426→ 426→// queue bar with skip/jump/replay + server-side caps (max 2 parallel
|
||||||
|
427→ 427→// extractions); music-service framework: Spotify + Apple Music are
|
||||||
|
428→ 428→// registered providers whose playback ships in a later update
|
||||||
|
429→ 429→// (logins already save, links answer with an honest notice)
|
||||||
|
430→ 430→// 6.6 — music service logins (Settings tab): YouTube / YouTube Music,
|
||||||
|
431→ 431→// SoundCloud, Spotify, Apple Music cookie login, encrypted on the
|
||||||
|
432→ 432→// visitor's computer; Link button gains playlist picking + uses the
|
||||||
|
433→ 433→// login to bypass bot walls
|
||||||
|
434→ 434→// 6.5 — Settings tab (replaces the parked Native tab): theme picker with
|
||||||
|
435→ 435→// base chrome (Royal Purple / Matte Black / Black + Purple Outline /
|
||||||
|
436→ 436→// Light) × selectable highlight color (Purple, Yellow, Blue, Green,
|
||||||
|
437→ 437→// Red, Orange), all per-visitor; simple OVE logo as the site favicon
|
||||||
|
438→ 438→// 6.4 — beat detection suspends when no music is playing/loaded (no ghost
|
||||||
|
439→ 439→// beats after Stop, BPM resets) + dark theme restyle: royal purple
|
||||||
|
440→ 440→// chrome with light-yellow highlights
|
||||||
|
441→ 441→// 6.3 — light/dark theme tick switch in the header (white theme via .light
|
||||||
|
442→ 442→// palette remap; per-visitor, persisted by next-themes)
|
||||||
|
443→ 443→// 6.2 — security hardening: read-only embedded library (server refuses all
|
||||||
|
444→ 444→// writes) + per-visitor "user created" presets stored in the browser
|
||||||
|
445→ 445→// 6.1 — preset library quality pass: every embedded stand-in is now a UNIQUE
|
||||||
|
446→ 446→// generated visual (16 families × seeded parameters), approx badge
|
||||||
|
447→ 447→// 6.0 — renamed to "Open Visualization Engine" + footer version stamp
|
||||||
|
448→ 448→// 5.0 — folder-tree Presets page (Task 9–11 state, restored in Task 12)
|
||||||
|
449→ 449→// 4.0 — link audio source (yt-dlp resolve + stream proxy)
|
||||||
|
450→ 450→
|
||||||
|
451→ 451→export const APP_NAME = 'Open Visualization Engine'
|
||||||
|
452→ 452→export const APP_SHORT = 'OVE'
|
||||||
|
453→ 453→export const APP_VERSION = '7.14'
|
||||||
|
454→ 454→
|
||||||
38
worklog.md
38
worklog.md
|
|
@ -2390,3 +2390,41 @@ BROWSER E2E (agent-browser + VLM, ALL PASS):
|
||||||
|
|
||||||
Stage Summary:
|
Stage Summary:
|
||||||
- v7.14 shipped: Arctic Rainbow is now a music-driven water spray -- slow parabolic rises, left/right arching falls, drop count tracking the music's intensity (dry silence), per-drop bass-hit hue shifts, and tapered RGB tails that appear only on the way down. All behavior is stateless script math (deterministic rand-seeded parabolas), so it is fully deterministic and reproducible; the library re-seed delivers it to every visitor, and the old white-rain version is gone.
|
- v7.14 shipped: Arctic Rainbow is now a music-driven water spray -- slow parabolic rises, left/right arching falls, drop count tracking the music's intensity (dry silence), per-drop bass-hit hue shifts, and tapered RGB tails that appear only on the way down. All behavior is stateless script math (deterministic rand-seeded parabolas), so it is fully deterministic and reproducible; the library re-seed delivers it to every visitor, and the old white-rain version is gone.
|
||||||
|
|
||||||
|
---
|
||||||
|
Task ID: 43
|
||||||
|
Agent: main (Z.ai Code)
|
||||||
|
Task: v7.14.1 (user request, Brainstorm preset session 3): the user tested the Brainstorm color-shift + blow-out script on their VPS and reported the blow-out FADED -- they wanted the colors to keep moving outward at constant speed and be REPLACED by new color at the center, never fading. Engine request: support a no-fade mode and additional blend modes (subtract, multiply, additive, replace, etc.).
|
||||||
|
|
||||||
|
DESIGN:
|
||||||
|
- The fade could not be tuned away: the persistence fade is a per-frame black fill (engine.ts loop), and persist was hard-capped at 0.98 (parser) / 0.985 (engine). A feedback blow-out therefore ALWAYS decayed.
|
||||||
|
- blend: replace = canvas 'source-over' with the CORE pass at FULL alpha (lines gain 1.0 instead of 0.9; dots alphaGain 1.0 instead of 0.85; solid fill alpha 1.0 instead of 0.65; halo pass 0.25): fresh strokes overwrite the pixels under them, so a figure redrawn over its own accumulating trails keeps its color instead of saturating to white. source-over can never exceed the source color, so nothing accumulates past what the script paints.
|
||||||
|
- blend: subtract = canvas 'difference' (|dst - src|): Canvas2D has no clamped true-subtract composite; difference is the classic approximation and shares the property the mode exists for -- it can never climb to white.
|
||||||
|
- blend: multiply / screen = the canvas composites of the same names; additive/alpha unchanged.
|
||||||
|
- persist widened 0..0.98 -> 0..1 (parser clamp + error string) and 0..0.985 -> 0..1 (engine clamp); at exactly 1 the fade fill is SKIPPED entirely -- a true zero-fade framebuffer. Old content then leaves the screen only through a bg* transform carrying it off-screen (or a replace repaint), which is exactly the user's model: "the blow-out itself is the cleanup".
|
||||||
|
|
||||||
|
Work Log:
|
||||||
|
- types.ts: BlendMode union extended to 'additive' | 'alpha' | 'multiply' | 'screen' | 'subtract' | 'replace' with a doc comment.
|
||||||
|
- script.ts: BLENDS set + header doc + error message list all six; persist range 0..1 with the v7.14.1 comment.
|
||||||
|
- engine.ts: BLEND_COMPOSITE map (blend -> GlobalCompositeOperation) replaces the old additive ternary; persistence block clamps to 1 and skips the fill at persist === 1; per-mode replace gains in the lines/dots/solid passes; file-header comment updated.
|
||||||
|
- help-panel.tsx: persist var doc (0..1, 1 = never fades) + .ava format block (blend list, persist range).
|
||||||
|
- version.ts: APP_VERSION 7.14.1 + full history entry. release-notes.ts: 7.14.1 "New blend modes + zero-fade trails" (2 added items, first-person voice).
|
||||||
|
- No library changes (EMBEDDED_SEED_VERSION stays 7.14; all 106 presets keep additive/alpha and their persist values -- verified untouched).
|
||||||
|
|
||||||
|
HEADLESS VALIDATION (/home/z/tmp-validate/blend/validate.mts, REAL parser/compiler, 33/33 PASS):
|
||||||
|
- Library regression: all 106 presets compile; zero presets re-interpret blend or shift persist past 0.98.
|
||||||
|
- All six blend values accepted (additive, alpha, multiply, screen, subtract, replace); 'lighten' and '' rejected; the invalid-blend error lists all six.
|
||||||
|
- persist parse/clamp: 1 -> 1, 1.5 -> 1, 100 -> 1, 0.985/0.98 unchanged, -0.5 -> 0; bad-persist error says 0..1.
|
||||||
|
- The v7.14.1 Brainstorm script end-to-end (240 frames x 584 points, engine env conventions): no runtime errors, all positions finite, all colors in range, bgzoom constant 1.035 every frame, bgrot magnitude constant 0.015, dir flips on 5/5 beats, hue advances 0.551 over 5 beats (~0.11 each).
|
||||||
|
|
||||||
|
BROWSER E2E (agent-browser, ALL PASS):
|
||||||
|
- Fresh load: title "OVE -- Open Visualization Engine (v7.14.1)".
|
||||||
|
- ZERO-FADE PROOF: dots field that draws 90 frames then skips forever -- brightPct 87.6% at t=3s AND 87.6% at t=15s (13.5s after drawing stopped), maxLum 240. Under the old 0.985 cap it would have been ~0% (0.985^810 ~= 5e-6).
|
||||||
|
- FADE-PATH REGRESSION: the same script at persist 0.859 -> brightPct 0.00% 1.5s after drawing stopped (normal fading fully intact).
|
||||||
|
- REPLACE PROOF: rotating ring (1, 0.7, 0.4) at persist 1, after 10s of repainting over itself the brightest ring pixels average RGB [255, 178, 101] = the EXACT script color, zero white-clamped pixels. Contrast run with blend additive: [255, 255, 255] with 25,064 white-clamped pixels -- the modes genuinely differ and replace does not advance to white.
|
||||||
|
- v7.14.1 Brainstorm on the demo track (FPS 56-60, PTS 584, BPM locked 132.9-142.5): radial-band analysis shows 100% bright pixels in ALL annulus bands 0.1-1.1 with age-shifted colors (teal center -> green outer = old hues riding outward); 5s later the center band color flipped cyan [4,133,171] -> magenta [161,0,199] while staying 96.8% bright = new color replacing old at the center, old color riding out. VLM: kaleidoscope flower at center, blow-out wedges to all edges, multiple distinct hues at different radii, nothing washed to white, no glitches.
|
||||||
|
- Stock library Brainstorm (additive, 0.859) applied: classic near-white flower with fading trails to black edges (VLM-confirmed geometry + fade; its white/gray tone is the script's own color math, r=g=b coefficients -- the stock hue var is unused), FPS 59 -- old presets render byte-for-byte as before.
|
||||||
|
- What's New: 7.14.1 "New blend modes + zero-fade trails" first with CURRENT badge, both items present. Console 0 errors / 0 warnings. Responsive: 375x812 no horizontal overflow; 1280x800 footer at viewport bottom (docH 800 = innerH). Test-browser localStorage cleared afterwards.
|
||||||
|
|
||||||
|
Stage Summary:
|
||||||
|
- v7.14.1 shipped: the script engine grew a real composite family (multiply, screen, subtract, replace) and a true zero-fade framebuffer (persist 1 skips the fade pass). The pairing the user asked for -- blend replace + persist 1 + bgzoom/bgrot feedback -- now renders a static-speed blow-out that never fades: colors ride outward at full brightness and the fresh drawing replaces the old color at the center. All existing presets keep their exact behavior (regression-verified headlessly and in-browser); the updated Brainstorm script was delivered to the user for VPS testing.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue