From 1277316d00bc4eddb0a332474c12d8e5b5f17407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87A=C4=9EAN=20TURGUT?= Date: Wed, 23 Sep 2026 17:23:58 +0300 Subject: [PATCH 1/2] fix(milkdrop): hue colour and fixed composite as MilkDrop draws them MilkDrop gives every composite shader the full hue colour, whatever the preset's fShader says ("since we don't know if shader uses it or not", milkdropfs.cpp:4122; BeatDrop's D3D9 code 4318). fShader scales the colour only on the fixed pipeline and in the shader MilkDrop writes for a preset without one. 52c7391 (#560) applied the fixed pipeline's rule to preset shaders as well, so with fidelity on the 914 corpus presets whose composite shader reads hue_shader and leaves fShader at 0 lost their colour, and 36 got only part of it. - Preset shaders get the colour at amount 1 again. - hueAt no longer flips y. MilkDrop weighs the corners by the composite grid's screen position with y at 1 on top (plugin.cpp:1475-1490), and our uv.y is 1 on top as well. Read back from the screen, corner 0 now sits top right as in MilkDrop; it sat bottom right. - On the fixed pipeline the amount is the file's fShader, blended linearly through a transition like MilkDrop's CBlendableFloat, not the per-frame value (16 presets write fshader in code), and it is no longer clamped to 1. - The fixed pipeline is drawn the way MilkDrop draws it. Every pass's corner colour is gamma x layer share x hue, put through COLOR_NORM (fixedCompWeights, replacing fixedGammaGain), so fShader 10 wraps into other colours as in MilkDrop (16 presets). The quad is interpolated as two triangles sharing the top-right/bottom-left edge, not bilinearly. A generated composite stage no longer gets a partial fShader twice. - Echo fading during a blend looks at the new side's blended echo alpha (m_fVideoEchoAlpha.eval), so an echo fades out even when the new preset has none. The legacy path keeps its own shader branch. Measured against main over the last 10 of 60 frames, up to 120 presets a group: - shader presets that do not read hue_shader: 120/120 identical; - fShader-0 readers: 95/120 changed, and median saturation went from 0.006 to 0.150; - full readers (orientation only): 90/120 changed; - partial readers: 28/36 changed; - fixed pipeline with fShader: 30/120 changed (median 0.5%); - fixed pipeline without fShader: rounding only (largest 0.36%). With fidelity off, the fixed pipeline and control presets are identical, and hue readers differ by one level in at most 0.1% of pixels (equal corner colours summed in another order). The fixed pipeline render check passes 72/72 within 2/255. 3 new tests and 3 test files reworked; 18/18 mutations are caught. The docs' test count follows. Refs #580, #560 --- README.md | 4 +- README.tr.md | 4 +- ROADMAP.md | 4 +- src/shared/milkdrop-shader.js | 14 +- src/shared/milkdrop.js | 70 +++++++-- src/visualizer/modes/milkdrop.js | 203 +++++++++++++++++---------- tests/milkdrop-fidelity-comp.test.js | 90 ++++++++++-- tests/milkdrop-fidelity.test.js | 24 ++-- tests/milkdrop-hue-shader.test.js | 119 +++++++++++----- 9 files changed, 375 insertions(+), 157 deletions(-) diff --git a/README.md b/README.md index 0b4d019..d75e135 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ [![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-111997.svg)](#build--distribution) [![Electron](https://img.shields.io/badge/Electron-43-47848F.svg)](https://www.electronjs.org/) [![Downloads](https://img.shields.io/github/downloads/CaYatur/SoundVisualizer/total?label=downloads)](https://github.com/CaYatur/SoundVisualizer/releases) -[![Tests](https://img.shields.io/badge/tests-2181%20passing-2ea043.svg)](#tests) +[![Tests](https://img.shields.io/badge/tests-2184%20passing-2ea043.svg)](#tests) [![cayadev.com](https://img.shields.io/badge/cayadev.com-e11d2a.svg)](https://cayadev.com) @@ -1299,7 +1299,7 @@ npm test npm start -- --smoke ``` -**2181 unit tests, all passing.** They are written to check answers, not to exercise lines: +**2184 unit tests, all passing.** They are written to check answers, not to exercise lines: - **Formulas** are checked against values derived by hand from their definitions — Viviani's curve staying on its sphere, the torus tube radius, Chladni's m↔n antisymmetry, every attractor diff --git a/README.tr.md b/README.tr.md index 771e520..47b22e1 100644 --- a/README.tr.md +++ b/README.tr.md @@ -12,7 +12,7 @@ [![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-111997.svg)](#paketleme--dağıtım) [![Electron](https://img.shields.io/badge/Electron-43-47848F.svg)](https://www.electronjs.org/) [![İndirme](https://img.shields.io/github/downloads/CaYatur/SoundVisualizer/total?label=indirme)](https://github.com/CaYatur/SoundVisualizer/releases) -[![Test](https://img.shields.io/badge/test-2181%20geçiyor-2ea043.svg)](#testler) +[![Test](https://img.shields.io/badge/test-2184%20geçiyor-2ea043.svg)](#testler) [![cayadev.com](https://img.shields.io/badge/cayadev.com-e11d2a.svg)](https://cayadev.com) @@ -1297,7 +1297,7 @@ npm test npm start -- --smoke ``` -**2181 birim testi, hepsi geçiyor.** Satır çalıştırmak için değil, cevap denetlemek için yazıldılar: +**2184 birim testi, hepsi geçiyor.** Satır çalıştırmak için değil, cevap denetlemek için yazıldılar: - **Formüller**, tanımlarından elle türetilmiş değerlerle sınanıyor — Viviani eğrisinin küre üzerinde kalması, simidin boru yarıçapı, Chladni'nin m↔n antisimetrisi, her çekicinin sınırlı diff --git a/ROADMAP.md b/ROADMAP.md index 0936204..8dd77b5 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -129,9 +129,9 @@ npm test npm start -- --smoke ``` -- **2181 unit tests, all passing** on `main`. 703 of those shipped in v3.1.0; +- **2184 unit tests, all passing** on `main`. 703 of those shipped in v3.1.0; 105 came with v3.1.1; 163 came with v3.1.2; 157 came with v3.1.3 — 1128 at - that tag — 469 more with v3.1.4, most of them from the MilkDrop work, and 584 + that tag — 469 more with v3.1.4, most of them from the MilkDrop work, and 587 on `main` since. Formulas are checked against values derived by hand from their definitions — Viviani's curve staying on its sphere, the diff --git a/src/shared/milkdrop-shader.js b/src/shared/milkdrop-shader.js index 6d16c8e..676bc3c 100644 --- a/src/shared/milkdrop-shader.js +++ b/src/shared/milkdrop-shader.js @@ -503,10 +503,16 @@ tamamı hacim gürültüsünü veriyor; iki boyutlu aşırı yükleme yine de duruyor, çünkü korpus dışında bir preset 2B sampler geçirirse eskiden derlenen shader birden derlenmez olurdu. */ - /* Dort kose renginin cift dogrusal karisimi. MilkDrop'un tarama sirasi - ekranin USTUNDEN baslıyor, bizim `uv.y` ise altta sifir: bu yuzden y - ters cevriliyor. Ters cevrilmezse renk gecisi dikeyde aynalanirdi. */ - 'vec3 hueAt(vec2 p){ float x = p.x; float y = 1.0 - p.y;' + + /* Dört köşe renginin çift doğrusal karışımı, MilkDrop'un ağırlıklarıyla + (milkdropfs.cpp:4146-4160; BeatDrop'un D3D9 hâli aynı): köşe 0 x·y, + 1 (1−x)·y, 2 x·(1−y), 3 (1−x)·(1−y). x ve y birleştirme ağının + EKRAN konumundan geliyor ve y ekranın ÜSTÜNDE 1 — ağ `sy`yi üst + satırda +1 kuruyor (plugin.cpp:1475-1490) — yani 0 üst-sağ, 1 üst-sol, + 2 alt-sağ, 3 alt-sol. Bizim `uv.y` de ekranın üstünde 1: ağın ilk + satırı altta, ve ekrandan geri okunarak doğrulandı. Burada + `1.0 - p.y` vardı ("MilkDrop'un taraması üstten başlıyor" diye) ve + renk geçişini dikeyde aynalıyordu (#580). */ + 'vec3 hueAt(vec2 p){ float x = p.x; float y = p.y;' + ' return hue_corner[0] * x * y + hue_corner[1] * (1.0 - x) * y' + ' + hue_corner[2] * x * (1.0 - y) + hue_corner[3] * (1.0 - x) * (1.0 - y); }', 'vec3 tex3D(sampler3D s, vec3 uv2){ return texture(s, uv2).xyz; }', diff --git a/src/shared/milkdrop.js b/src/shared/milkdrop.js index 02ec8d2..97b2555 100644 --- a/src/shared/milkdrop.js +++ b/src/shared/milkdrop.js @@ -1642,20 +1642,70 @@ return (o % 2 !== 0 ? 1 : 0) | (o >= 2 ? 2 : 0); } - /* Sabit yolun gaması (milkdropfs.cpp:3955-4010): görüntü tam katları - kadar üst üste TOPLANARAK çiziliyor, son kat kesirli. Yankı açıkken - ilk çizim zaten tam; ek çizim ancak gama 1'i aştığında var, yani - 1'in altındaki gama yankıyla birlikte HİÇ uygulanmıyor. Yankı yokken - tek geçiş gamanın kendisiyle çiziliyor. */ - function fixedGammaGain(g, echoOn) { - const gamma = Math.fround(Number(g) || 0); - if (echoOn) return gamma > 0.001 && Math.trunc(gamma - 0.0001) >= 1 ? gamma : 1; - return gamma > 0 ? gamma : 0; + /* SABİT BİRLEŞTİRMENİN KÖŞE AĞIRLIKLARI (#580; milkdropfs.cpp:3907-4003, + BeatDrop'un D3D9 hâli 4090-4180 aynı). + + MilkDrop sabit yolda görüntüyü dokulu bir dörtgenle BİRKAÇ KEZ + çiziyor: ilki yazıyor, gerisi üstüne ekliyor. Her çizimin köşe rengi + (o çizimin gaması) × (katmanın payı) × (ton rengi) ve tepe rengi + yolundan, yani COLOR_NORM'dan geçiyor: bayta kırpılıyor, 1'i aşan ya + da eksiye düşen değer SARIYOR. Ekrandaki sonuç + + ana doku × Σ ana çizimlerin rengi + yankı dokusu × Σ yankı çizimlerinin rengi + + ve bu işlev iki toplamı köşe başına veriyor. Ton [0,1] içindeyken + toplam gama × pay × tonun bayta kırpılmışı; `fShader` 1'in üstündeyse + ton eksiye iniyor ve sarma onu başka bir renge çeviriyor (korpusta + sabit yolda 16 preset `fShader=10` yazıyor). + + - Yankı açık (saydamlık > 0,001): iki katman, ana `1 − a`, yankı `a` + payıyla. Her biri bir kez çiziliyor; gama 0,001'in üstündeyse + `(int)(gama − 0,0001)` kez daha, son tekrarın gaması kesirli kısım. + Yani 1'in altındaki gama yankıyla HİÇ uygulanmıyor. + - Yankı kapalı: `(int)(gama − 0,001) + 1` geçiş, sonuncunun gaması + kalan. Gama −0,999'un altındaysa hiç geçiş yok, dörtgen çizilmiyor + (bizde siyah). + + MilkDrop 8 bitlik tamponda her çizimden sonra yuvarlıyor; burada + toplam bir kez yuvarlanıyor. Hesap float32, MilkDrop'taki gibi. + `shade`: 12 sayı, köşe sırası üst-sol, üst-sağ, alt-sol, alt-sağ + (MilkDrop'un dörtgeni v3[0..3]). `out` verilirse dizileri yeniden + kullanılıyor. */ + function fixedCompWeights(gamma, echoAlpha, shade, out) { + const f = Math.fround; + const g = f(Number(gamma) || 0); + const a = f(Number(echoAlpha) || 0); + const main = out && out.main ? out.main.fill(0) : new Float32Array(12); + const echo = out && out.echo ? out.echo.fill(0) : new Float32Array(12); + // COLOR_NORM float32'de: (int)(x * 255) & 0xFF + const cn = (x) => (Math.trunc(f(x * 255)) & 0xFF) / 255; + const draw = (dst, k) => { for (let i = 0; i < 12; i++) dst[i] += cn(f(k * shade[i])); }; + /* Tam katlar 256'da kesiliyor: sıfır olmayan her çizim en az 1/255 + ekliyor, yani 255 kattan sonra kanal zaten doymuş. */ + const MAX_DRAWS = 256; + const echoOn = a > 0.001; + if (echoOn) { + for (let layer = 0; layer < 2; layer++) { + const mix = layer === 1 ? a : f(1 - a); + const dst = layer === 1 ? echo : main; + draw(dst, mix); + if (g > 0.001) { + const n = Math.trunc(f(g - f(0.0001))); + for (let r = Math.max(0, n - MAX_DRAWS); r < n; r++) { + draw(dst, f((r === n - 1 ? f(g - n) : 1) * mix)); + } + } + } + } else { + const n = Math.trunc(f(g - f(0.001))) + 1; + for (let p = Math.max(0, n - MAX_DRAWS); p < n; p++) draw(main, p === n - 1 ? f(g - p) : 1); + } + return { main, echo, echoOn }; } const api = { tokenize, parse, compile, Pool, FUNCS, parseMilk, Preset, clampColor, colorNorm, md2Versions, stagePlan, genWarpText, genCompText, - echoFlipBits, fixedGammaGain }; + echoFlipBits, fixedCompWeights }; if (typeof module !== 'undefined' && module.exports) module.exports = api; if (typeof window !== 'undefined') window.SVMilkdrop = api; })(); diff --git a/src/visualizer/modes/milkdrop.js b/src/visualizer/modes/milkdrop.js index 1370dd8..03cc166 100644 --- a/src/visualizer/modes/milkdrop.js +++ b/src/visualizer/modes/milkdrop.js @@ -254,27 +254,45 @@ in vec2 vUV; in float vBlend; out vec4 outColor; uniform sampler2D uSrc; -uniform float uGamma; -uniform float uEchoAlpha; +uniform float uGamma; // eski biçim +uniform float uEchoAlpha; // eski biçimde yankının payı; MilkDrop biçiminde yalnız "yankı var" uniform float uEchoZoom; uniform int uEchoOrient; uniform vec4 uFx; // brighten, darken, solarize, invert uniform float uFxMd2; // 1: MilkDrop 2'nin sabit yolunun biçimleri (#580) -uniform vec3 uHue[4]; // dört köşe rengi: üst-sol, üst-sağ, alt-sol, alt-sağ +/* MilkDrop biçimi: iki katmanın köşe ağırlıkları, köşe sırası üst-sol, + üst-sağ, alt-sol, alt-sağ. Her biri o katmanın çizimlerinin köşe + renklerinin toplamı — gama, pay ve ton rengi içinde (M.fixedCompWeights). */ +uniform vec3 uWMain[4]; +uniform vec3 uWEcho[4]; +/* MilkDrop'un dörtgeni İKİ ÜÇGEN (şerit v0 v1 v2 v3): ortak kenar + üst-sağdan alt-sola. Köşe rengi her üçgenin içinde doğrusal, çift + doğrusal değil — ortada dört köşenin değil 1 ile 2'nin ortalaması. */ +vec3 quad(vec3 w[4]) { + float x = vUV.x, y = vUV.y; + if (y >= x) return (y - x) * w[0] + x * w[1] + (1.0 - y) * w[2]; + return y * w[1] + (1.0 - x) * w[2] + (x - y) * w[3]; +} +vec2 echoUV() { + vec2 e = (vUV - 0.5) / max(0.001, uEchoZoom) + 0.5; + if (uEchoOrient == 1 || uEchoOrient == 3) e.x = 1.0 - e.x; + if (uEchoOrient == 2 || uEchoOrient == 3) e.y = 1.0 - e.y; + return e; +} void main(){ - vec3 c = texture(uSrc, vUV).rgb; - if (uEchoAlpha > 0.001) { - vec2 e = (vUV - 0.5) / max(0.001, uEchoZoom) + 0.5; - if (uEchoOrient == 1 || uEchoOrient == 3) e.x = 1.0 - e.x; - if (uEchoOrient == 2 || uEchoOrient == 3) e.y = 1.0 - e.y; - c = mix(c, texture(uSrc, e).rgb, uEchoAlpha); + vec3 c; + if (uFxMd2 > 0.5) { + /* MilkDrop 2: görüntü köşe rengiyle çarpılarak birkaç kez çiziliyor ve + toplanıyor (milkdropfs.cpp:3907-4003). Gama, yankının payı ve dört + köşe rengi (fShader) ağırlıkların içinde, çizim başına bayta + sarılmış olarak. */ + c = texture(uSrc, vUV).rgb * quad(uWMain); + if (uEchoAlpha > 0.001) c += texture(uSrc, echoUV()).rgb * quad(uWEcho); + } else { + c = texture(uSrc, vUV).rgb; + if (uEchoAlpha > 0.001) c = mix(c, texture(uSrc, echoUV()).rgb, uEchoAlpha); + c *= uGamma; } - /* MilkDrop dort kose rengini SABIT yolda da uyguluyor: tam ekran dortgeni - bu renklerle ciziliyor ve donanim aralarini dolduruyor - (milkdropfs.cpp:3857-3884, 3940-3946). Shader'li yolda ayni renkler - hue_shader olarak gidiyordu; sabit yolda hic uygulanmiyordu. */ - c *= mix(mix(uHue[2], uHue[3], vUV.x), mix(uHue[0], uHue[1], vUV.x), vUV.y); - c *= uGamma; c = clamp(c, 0.0, 1.0); /* MilkDrop'un MD1 donemi sabit efektleri. Bunlar shader'dan onceki surumlerden kalma ama eski presetlerin cogu hala kullaniyor; yoklugunda @@ -1147,7 +1165,8 @@ void main(){ uEchoOrient: gl.getUniformLocation(this.compFixed, 'uEchoOrient'), uFx: gl.getUniformLocation(this.compFixed, 'uFx'), uFxMd2: gl.getUniformLocation(this.compFixed, 'uFxMd2'), - uHue: gl.getUniformLocation(this.compFixed, 'uHue[0]'), + uWMain: gl.getUniformLocation(this.compFixed, 'uWMain[0]'), + uWEcho: gl.getUniformLocation(this.compFixed, 'uWEcho[0]'), }; this.locBlur = { uSrc: gl.getUniformLocation(this.blurProg, 'uSrc'), @@ -2326,13 +2345,19 @@ void main(){ preset yarı parlaklıkta çıkıyordu (varsayılan artık kare başı tabanında, shared/milkdrop.js MD2_PF_DEFAULTS); - yön `(int)echo_orient % 4` (shared/milkdrop.js `echoFlipBits`); - - gama görüntüyü üst üste toplayarak çiziyor ve yankıyla birlikte - 1'in altında HİÇ uygulanmıyor (`fixedGammaGain`); - - GEÇİŞTE iki presetin de dosyasında yankı varsa (saydamlık 0,01'in - üstünde) ve yönleri farklıysa, yankı atlama noktasına kadar ESKİ - yönle sönüyor, sonra yeni yönle yeniden beliriyor — yön bir anda + - gama ham dönüyor: görüntüyü üst üste toplayarak çizen geçişler ve + yankıyla birlikte 1'in altında uygulanmaması köşe ağırlıklarında + (shared/milkdrop.js `fixedCompWeights`); + - GEÇİŞTE iki tarafta da yankı varsa (saydamlık 0,01'in üstünde) ve + yönleri farklıysa, yankı atlama noktasına kadar ESKİ yönle + sönüyor, sonra yeni yönle yeniden beliriyor — yön bir anda dönmüyor. Karşılaştırılan DOSYADAKİ değerler; MilkDrop da onlara - bakıyor, denklemlerin yazdığına değil. */ + bakıyor, denklemlerin yazdığına değil. Yeni tarafın saydamlığı + geçiş boyunca KARIŞAN dosya değeri (`m_fVideoEchoAlpha.eval`), + eski tarafınki geçiş başındaki değer: yeni presette yankı kapalı + olsa da karışan değer 0,01'i aşıyor ve eski yankı yine sönerek + gidiyor (#580'in ilk hâlinde yeni presetin ham değerine + bakılıyordu ve yankı bir anda dönüyordu). */ _fixedCompInputs(Pp) { const M = window.SVMilkdrop; /* Gama ve yakınlaşma havuzdan: dosyanın yazmadığı adda MilkDrop'un @@ -2347,7 +2372,7 @@ void main(){ const v = P && P.file && P.file.params ? P.file.params[k] : undefined; return typeof v === 'number' && isFinite(v) ? v : d; }; - const na = fp(Pp, 'fvideoechoalpha', 0); + const na = this._fileVal('fvideoechoalpha', 0); const oa = fp(O, 'fvideoechoalpha', 0); const no = Math.trunc(fp(Pp, 'nvideoechoorientation', 0)); const oo = Math.trunc(fp(O, 'nvideoechoorientation', 0)); @@ -2362,7 +2387,26 @@ void main(){ } } } - return { alpha, zoom, orient, gain: M.fixedGammaGain(Pp.get('gamma'), alpha > 0.001) }; + return { alpha, zoom, orient, gamma: Pp.get('gamma') }; + } + + /* DOSYADAN OKUNAN, KARE DEĞİŞKENİ OLMAYAN ayarlar — `fShader` gibi. + MilkDrop bunları denklemlere açmıyor: kodun aynı adla yazdığı değer + bir kullanıcı değişkeni olarak kalıyor. Geçişte eski ve yeni presetin + dosyadaki değeri arasında DOĞRUSAL, ham ilerlemeyle karışıyorlar + (`CBlendableFloat::eval`, state.cpp:1933-1953) — kosinüs eğrisi yok. + `key` dosyanın küçük harfli anahtarı; yazılmamışsa `dflt`, yani + MilkDrop'un varsayılanı. */ + _fileVal(key, dflt) { + const get = (P) => { + const v = P && P.file && P.file.params ? P.file.params[key] : undefined; + return typeof v === 'number' && isFinite(v) ? v : dflt; + }; + const cur = get(this.preset); + const O = this.oldPreset; + if (!O || !(this.blendProg < 1)) return cur; + const p = Math.max(0, Math.min(1, this.blendProg)); + return get(O) * (1 - p) + cur * p; } /* MilkDrop'un dönme matrisleri: rot_s/d/f/vf/uf/rand 1..4. @@ -2472,47 +2516,51 @@ void main(){ return L; } - /* DÖRT KÖŞE RENGİ (`hue_shader`). Hem shader'lı yol (uniform) hem de - sabit birleştirme yolu (tepe rengi) bunu kullanıyor; MilkDrop ikisini - de aynı `shade[4][3]` dizisinden besliyor (milkdropfs.cpp:3857-3884). - Miktar presetin `fShader`ı: 0,001'in altındaysa dört köşe de beyaz. */ - _hueCorners(P, t, rand) { + /* DÖRT KÖŞE RENGİ. MilkDrop her kare dört köşeye ayrı fazlarla bir renk + hesaplıyor — sabit yolda (milkdropfs.cpp:3857-3884) ve shader'lı + yolda (4122-4140) aynı formül — ve rengi beyaza doğru `amt` oranıyla + karıştırıyor. Oranı ÇAĞIRAN veriyor, çünkü iki yol ayrı: + - shader'lı yol HER ZAMAN 1: MilkDrop "shader kullanıyor mu + bilmiyoruz" diye `hue_shader`a tam rengi veriyor (4122, BeatDrop + 4318); `fShader` yalnız kendi yazdığı birleştirme metninde çarpan. + 52c7391 (#560) oranı buraya da uyguluyordu ve `fShader`ı 0 olan + 914 preset rengini kaybediyordu (#580); + - sabit yol: presetin DOSYADAKİ `fShader`ı, geçişte doğrusal karışan + (`_fileVal`); 0,001'in altındaysa renk hiç hesaplanmıyor, köşeler + beyaz kalıyor. + Oran kenetlenmiyor: 1'in üstünde renk 0,5'in altına, eksiye iniyor ve + sabit yolda COLOR_NORM onu sarıyor (M.fixedCompWeights). + Dizideki sıra yalnız köşenin NUMARASI; ekrandaki yerini iki yol + kendisi veriyor ve MilkDrop'ta da ayrı: sabit yolun dörtgeninde 0 + üst-sol, shader'ın `hueAt`inde 0 üst-sağ. Uyum kapalıyken motorun + eski tek rengi. */ + _hueCorners(amt, t, rand) { const accurate = this._wantAcc !== false; const rs = rand || this.randPreset || [0, 0, 0, 0]; const hc = this._hueBuf || (this._hueBuf = new Float32Array(12)); - /* RENGİN MİKTARI presetin `fShader`ından. MilkDrop dört köşe rengini - hesapladıktan sonra beyaza doğru bu oranla karıştırıyor ve oran - 0,001'in altındaysa hiç hesaplamıyor: dört köşe de (1,1,1) - kalıyor (milkdropfs.cpp:3857-3876). Varsayılan 0 - (state.cpp:548). Motor rengi HER presete veriyordu: korpusta - `hue_shader` okuyan 1.239 presetin 914'ü `fShader`ı sıfır - bırakıyor, yani MilkDrop'ta hiç renk almayan bir görüntüyü - renklendiriyorduk; 36'sı da ara bir oran yazıyor. */ - const amt = accurate - ? Math.max(0, Math.min(1, +(P && P.get('fshader')) || 0)) : 1; - for (let i = 0; i < 4; i++) { - let r, g, b; - if (accurate && amt <= 0.001) { - r = 1; g = 1; b = 1; - } else if (accurate) { - const k = i; - r = 0.6 + 0.3 * Math.sin(t * 30 * 0.0143 + 3 + k * 21 + rs[3]); - g = 0.6 + 0.3 * Math.sin(t * 30 * 0.0107 + 1 + k * 13 + rs[1]); - b = 0.6 + 0.3 * Math.sin(t * 30 * 0.0129 + 6 + k * 9 + rs[2]); - const mx = Math.max(r, g, b) || 1; - r = 0.5 + 0.5 * (r / mx); - g = 0.5 + 0.5 * (g / mx); - b = 0.5 + 0.5 * (b / mx); - r = r * amt + (1 - amt); - g = g * amt + (1 - amt); - b = b * amt + (1 - amt); - } else { - r = 0.5 + 0.5 * Math.sin(t * 0.31); - g = 0.5 + 0.5 * Math.sin(t * 0.31 + 2.09); - b = 0.5 + 0.5 * Math.sin(t * 0.31 + 4.19); - } - hc[i * 3] = r; hc[i * 3 + 1] = g; hc[i * 3 + 2] = b; + for (let i = 0; i < 4; i++) { + let r, g, b; + if (accurate && !(amt > 0.001)) { + r = 1; g = 1; b = 1; + } else if (accurate) { + const k = i; + r = 0.6 + 0.3 * Math.sin(t * 30 * 0.0143 + 3 + k * 21 + rs[3]); + g = 0.6 + 0.3 * Math.sin(t * 30 * 0.0107 + 1 + k * 13 + rs[1]); + b = 0.6 + 0.3 * Math.sin(t * 30 * 0.0129 + 6 + k * 9 + rs[2]); + const mx = Math.max(r, g, b) || 1; + r = 0.5 + 0.5 * (r / mx); + g = 0.5 + 0.5 * (g / mx); + b = 0.5 + 0.5 * (b / mx); + r = r * amt + (1 - amt); + g = g * amt + (1 - amt); + b = b * amt + (1 - amt); + } else { + r = 0.5 + 0.5 * Math.sin(t * 0.31); + g = 0.5 + 0.5 * Math.sin(t * 0.31 + 2.09); + b = 0.5 + 0.5 * Math.sin(t * 0.31 + 4.19); } + hc[i * 3] = r; hc[i * 3 + 1] = g; hc[i * 3 + 2] = b; + } return hc; } @@ -2640,8 +2688,12 @@ void main(){ Anahtar KAPALIYKEN dort koseye de AYNI renk gidiyor: yapi ayni kaliyor (yine dort kose, yine ayni shader), yalnizca degerler - motorun eski tek-renk davranisini veriyor. */ - if (L.hue_corner) gl.uniform3fv(L.hue_corner, this._hueCorners(ctx.P, t, rand)); + motorun eski tek-renk davranisini veriyor. + + Oran HER ZAMAN 1, presetin `fShader`ı ne olursa olsun: MilkDrop + shader'a tam rengi veriyor ve kullanıp kullanmamayı shader'a + bırakıyor (milkdropfs.cpp:4122). Geçişte de hesaplanıyor. */ + if (L.hue_corner) gl.uniform3fv(L.hue_corner, this._hueCorners(1, t, rand)); /* Presetin kendisi bu uniform'ları okuyabiliyor (`b1n`/`b1x` olarak yazıp shader'da `blur1_min` diye geri okuyor; korpusta altı preset @@ -3455,8 +3507,18 @@ void main(){ const acc = this._wantAcc !== false; if (acc) { const f = this._fixedCompInputs(Pp); - gl.uniform1f(this.locComp.uGamma, f.gain); - gl.uniform1f(this.locComp.uEchoAlpha, f.alpha); + /* Dört köşe rengi de burada: MilkDrop tam ekran dörtgenini bu + renklerle çiziyor (milkdropfs.cpp:3857-3884, 3940-3946). + Korpusta comp shader'ı olmayan 2.128 presetin 631'i sıfırdan + büyük bir `fShader` yazıyor. Oran DOSYADAN, geçişte doğrusal + karışarak; renk dizisi paylaşılan tampon, hemen ağırlıklara + dönüşüyor (çizim başına COLOR_NORM, M.fixedCompWeights). */ + const shade = this._hueCorners(this._fileVal('fshader', 0), this.time, this.randPreset); + const w = window.SVMilkdrop.fixedCompWeights(f.gamma, f.alpha, shade, + this._compW || (this._compW = { main: new Float32Array(12), echo: new Float32Array(12) })); + gl.uniform3fv(this.locComp.uWMain, w.main); + gl.uniform3fv(this.locComp.uWEcho, w.echo); + gl.uniform1f(this.locComp.uEchoAlpha, w.echoOn ? 1 : 0); gl.uniform1f(this.locComp.uEchoZoom, f.zoom); gl.uniform1i(this.locComp.uEchoOrient, f.orient); } else { @@ -3470,17 +3532,6 @@ void main(){ Pp.get('brighten') ? 1 : 0, Pp.get('darken') ? 1 : 0, Pp.get('solarize') ? 1 : 0, Pp.get('invert') ? 1 : 0); if (this.locComp.uFxMd2) gl.uniform1f(this.locComp.uFxMd2, acc ? 1 : 0); - /* Dört köşe rengi burada da: MilkDrop tam ekran dörtgenini bu - renklerle çiziyor (milkdropfs.cpp:3940-3946), shader'lı yolla - aynı `shade` dizisinden. Korpusta comp shader'ı olmayan 2.129 - presetin 631'i sıfırdan büyük bir `fShader` yazıyor; onlarda - ekran boyunca gezen bu ton hiç çizilmiyordu. Uyum kapalıyken - beyaz: eski sabit yol rengi hiç uygulamıyordu. */ - if (this.locComp.uHue) { - gl.uniform3fv(this.locComp.uHue, this._wantAcc !== false - ? this._hueCorners(Pp, this.time, this.randPreset) - : (this._hueWhite || (this._hueWhite = new Float32Array(12).fill(1)))); - } } gl.bindVertexArray(this.vao); gl.drawElements(gl.TRIANGLES, this.indexCount, gl.UNSIGNED_INT, 0); diff --git a/tests/milkdrop-fidelity-comp.test.js b/tests/milkdrop-fidelity-comp.test.js index eaf9974..e6a2788 100644 --- a/tests/milkdrop-fidelity-comp.test.js +++ b/tests/milkdrop-fidelity-comp.test.js @@ -99,18 +99,43 @@ test('sabit yol yankı yönü: (int)x % 4, C işaret kuralıyla', () => { for (const [v, want] of cases) assert.strictEqual(M.echoFlipBits(v), want, String(v)); }); -test('sabit yol gaması: yankıyla birlikte 1\'in altı uygulanmıyor', () => { - const on = (g) => M.fixedGammaGain(g, true); - const off = (g) => M.fixedGammaGain(g, false); - assert.strictEqual(on(0.5), 1); - assert.strictEqual(on(0), 1); - assert.strictEqual(on(1), 1); - assert.strictEqual(on(1.00005), 1, 'kırpma sınırı 1,0001'); - assert.strictEqual(on(1.5), 1.5); - assert.strictEqual(on(2), 2); - assert.strictEqual(off(0.5), 0.5); - assert.strictEqual(off(0), 0, 'yankısız gama 0 siyah'); - assert.strictEqual(off(2), 2); +/* Sabit yolun gaması ve yankısı MilkDrop'un ÇİZİMLERİ olarak: her çizimin + köşe rengi gama × pay × ton ve COLOR_NORM'dan geçiyor (bayta kırpma, + taşanı sarma); ekrandaki çarpan çizimlerin toplamı + (milkdropfs.cpp:3907-4003). */ +test('sabit yol gaması ve yankısı: MilkDrop\'un çizimleri, çizim başına COLOR_NORM', () => { + const W = (g, a, s) => M.fixedCompWeights(g, a, new Array(12).fill(s === undefined ? 1 : s)); + const b = (x) => Math.trunc(Math.fround(x * 255)) / 255; // [0,1] içinde bayta kırpma + const near = (v, want, msg) => assert.ok(Math.abs(v - want) < 1e-6, msg + ': ' + v + ' ≠ ' + want); + // Yankı yok: (int)(g − 0,001) + 1 geçiş, sonuncusunun gaması kalan + near(W(2, 0).main[0], 2, 'gama 2 iki tam çizim'); + near(W(1.5, 0).main[0], 1 + b(0.5), 'gama 1,5: 1 + bayta kırpılmış 0,5'); + near(W(0.5, 0).main[0], b(0.5), 'gama 0,5 tek geçiş'); + near(W(0, 0).main[0], 0, 'yankısız gama 0 siyah'); + near(W(-1, 0).main[0], 0, 'gama −0,999 altında hiç çizim yok'); + near(W(-0.9995, 0).main[0], 0, 'sınır (int)(g − 0,001): −0,9995 de çizilmiyor'); + near(W(-0.5, 0).main[0], 129 / 255, 'eksi gama bayta SARIYOR'); + assert.strictEqual(W(2, 0).echoOn, false); + assert.ok(W(2, 0).echo.every((v) => v === 0), 'yankısız yankı katmanı boş'); + // Yankı açık: iki katman bir kez paylarıyla; ek çizim (int)(g − 0,0001) kez + let w = W(0.5, 0.5); + assert.strictEqual(w.echoOn, true); + near(w.main[0], b(0.5), '1\'in altındaki gama yankıyla uygulanmıyor (ana)'); + near(w.echo[0], b(0.5), '1\'in altındaki gama yankıyla uygulanmıyor (yankı)'); + near(W(1.00005, 0.5).main[0], b(0.5), 'kırpma sınırı 1,0001'); + w = W(2.5, 0.3); + near(w.main[0], 2 * b(0.7) + b(0.35), 'ana: pay, 1 × pay, 0,5 × pay'); + near(w.echo[0], 2 * b(0.3) + b(0.15), 'yankı: aynı çizimler'); + near(W(1, 0.0005).main[0], 1, 'saydamlık 0,001 ve altı yankı sayılmıyor'); + // Ton eksiye inerse (fShader 1'in üstünde) sarma başka bir renk veriyor + near(W(1, 0, -0.5).main[0], 129 / 255, 'ton −0,5'); + near(W(1, 0, -2.3).main[0], (Math.trunc(Math.fround(-2.3 * 255)) & 0xFF) / 255, 'ton −2,3'); + // Çok büyük gama doyuruyor; döngü sınırlı + assert.ok(W(1e6, 0).main[0] >= 1); + // Köşeler ayrı ayrı, dizideki sırasıyla + const s = [0.5, 0.6, 0.7, 0.8, 0.9, 1, 0.55, 0.65, 0.75, 0.85, 0.95, 0.52]; + const m2 = M.fixedCompWeights(1, 0, s).main; + s.forEach((v, i) => near(m2[i], b(v), 'köşe bileşeni ' + i)); }); // ------------------------------------------------------- varsayılanlar @@ -311,6 +336,15 @@ test('motor: geçişte yönü farklı iki yankı sönüp yeniden beliriyor', () f = m._fixedCompInputs(m.preset); assert.strictEqual(f.alpha, 0.5); assert.strictEqual(f.orient, 2); + /* Yeni presette yankı kapalı: yeni tarafın saydamlığı geçişte KARIŞAN + dosya değeri (m_fVideoEchoAlpha.eval), o 0,01'in üstünde kaldıkça eski + yankı yine eski yönle sönerek gidiyor */ + m.preset = mk(0, 0); + m.oldPreset = mk(1, 0.5); + m.blendProg = 0.25; + assert.strictEqual(m._fixedCompInputs(m.preset).orient, 1, 'yeni tarafta yankı yok: yine eski yön'); + m.blendProg = 0.999; + assert.strictEqual(m._fixedCompInputs(m.preset).orient, 0, 'karışan değer 0,0005: sönüm yok'); // Geçiş yokken yön (int) % 4 m.oldPreset = null; m.preset = mk(5, 0.5); @@ -327,5 +361,35 @@ test('sabit birleştirme shader\'ı: MilkDrop 2 biçimleri anahtarın arkasında // Sabit geçiş uyum açıkken girdilerini MilkDrop'un kuralından alıyor assert.match(code, /const acc = this\._wantAcc !== false;\s*if \(acc\) \{\s*const f = this\._fixedCompInputs\(Pp\);/); assert.match(code, /gl\.uniform1i\(this\.locComp\.uEchoOrient, f\.orient\);/); - assert.match(code, /gl\.uniform1f\(this\.locComp\.uGamma, f\.gain\);/); + // Gama, yankının payı ve ton köşe ağırlıklarında; tonun oranı dosyadan + assert.match(code, /const shade = this\._hueCorners\(this\._fileVal\('fshader', 0\), this\.time, this\.randPreset\);/); + assert.match(code, /window\.SVMilkdrop\.fixedCompWeights\(f\.gamma, f\.alpha, shade,/); + assert.match(code, /gl\.uniform3fv\(this\.locComp\.uWMain, w\.main\);/); + assert.match(code, /gl\.uniform3fv\(this\.locComp\.uWEcho, w\.echo\);/); + // MilkDrop biçiminde uEchoAlpha yalnız "yankı dalı var" bayrağı + assert.match(code, /gl\.uniform1f\(this\.locComp\.uEchoAlpha, w\.echoOn \? 1 : 0\);/); + assert.match(code, /c = texture\(uSrc, vUV\)\.rgb \* quad\(uWMain\);\s*if \(uEchoAlpha > 0\.001\) c \+= texture\(uSrc, echoUV\(\)\)\.rgb \* quad\(uWEcho\);/); + // Eski biçim kendi dalında, değişmeden + assert.match(code, /c = texture\(uSrc, vUV\)\.rgb;\s*if \(uEchoAlpha > 0\.001\) c = mix\(c, texture\(uSrc, echoUV\(\)\)\.rgb, uEchoAlpha\);\s*c \*= uGamma;/); +}); + +/* MilkDrop'un sabit dörtgeni iki üçgen (şerit v0 v1 v2 v3; ortak kenar + üst-sağdan alt-sola): köşe rengi her üçgenin içinde DOĞRUSAL. Shader'daki + formül burada sayılarla koşturuluyor — köşelerde köşenin kendisi, ortada + 1 ile 2'nin ortalaması (çift doğrusalda dördünün ortalaması olurdu). */ +test('sabit dörtgen: köşe rengi iki üçgende doğrusal, ortak kenar üst-sağ → alt-sol', () => { + const code = read('src/visualizer/modes/milkdrop.js'); + const q = /vec3 quad\(vec3 w\[4\]\) \{\s*float x = vUV\.x, y = vUV\.y;\s*if \(y >= x\) return ([^;]+);\s*return ([^;]+);\s*\}/.exec(code); + assert.ok(q, 'quad() bulunamadı'); + const fn = new Function('w', 'x', 'y', 'return y >= x ? (' + q[1] + ') : (' + q[2] + ');'); + const w = [10, 20, 30, 40]; // 0 üst-sol, 1 üst-sağ, 2 alt-sol, 3 alt-sağ + assert.strictEqual(fn(w, 0, 1), 10, 'üst-sol'); + assert.strictEqual(fn(w, 1, 1), 20, 'üst-sağ'); + assert.strictEqual(fn(w, 0, 0), 30, 'alt-sol'); + assert.strictEqual(fn(w, 1, 0), 40, 'alt-sağ'); + assert.strictEqual(fn(w, 0.5, 0.5), 25, 'orta: 1 ile 2\'nin ortalaması'); + // Ortak kenarın iki yanı birleşiyor + for (const t of [0.1, 0.3, 0.7]) { + assert.ok(Math.abs(fn(w, t, t + 1e-9) - fn(w, t + 1e-9, t)) < 1e-6, 'kenarda süreksiz: ' + t); + } }); diff --git a/tests/milkdrop-fidelity.test.js b/tests/milkdrop-fidelity.test.js index f1fead6..4a6fc37 100644 --- a/tests/milkdrop-fidelity.test.js +++ b/tests/milkdrop-fidelity.test.js @@ -99,12 +99,15 @@ test('çeviri: hueAt dört köşeyi çift doğrusal karıştırıyor', () => { assert.match(r.glsl, /hue_corner\[3\] \* \(1\.0 - x\) \* \(1\.0 - y\)/); }); -/* MilkDrop'un tarama sırası ekranın ÜSTÜNDEN başlıyor, bizim `uv.y` ise - altta sıfır. Ters çevrilmezse renk geçişi dikeyde aynalanır — hata - vermez, yalnız yanlış görünür. */ -test('çeviri: hueAt dikeyde ters çeviriyor', () => { +/* MilkDrop'un ağırlığındaki y birleştirme ağının EKRAN konumu ve ekranın + üstünde 1 (plugin.cpp:1475-1490, `sy` üst satırda +1); bizim `uv.y` de + üstte 1. Burada eskiden `1.0 - p.y` vardı ve renk geçişi dikeyde + aynalanıyordu (#580) — hata vermez, yalnız yanlış görünür. Ekrandaki + yerleşim bu testin sınayamadığı şey; o ölçümle doğrulandı. */ +test('çeviri: hueAt y\'yi çevirmiyor (MilkDrop\'ta y ekranın üstünde 1)', () => { const r = T.translate('shader_body { ret = hue_shader; }'); - assert.match(r.glsl, /float y = 1\.0 - p\.y;/); + assert.match(r.glsl, /float y = p\.y;/); + assert.doesNotMatch(r.glsl, /float y = 1\.0 - p\.y;/); }); test('çeviri: warp aşaması da hue_shader alıyor', () => { @@ -114,10 +117,11 @@ test('çeviri: warp aşaması da hue_shader alıyor', () => { test('motor: köşe renkleri tek seferde yükleniyor', () => { assert.match(CODE, /L\.hue_corner = u\('hue_corner\[0\]'\)/); - /* Renkler ayrı bir yöntemde: shader'lı yol uniform olarak, sabit - birleştirme yolu da tepe rengi olarak aynı diziyi kullanıyor. */ - assert.match(CODE, /gl\.uniform3fv\(L\.hue_corner, this\._hueCorners\(ctx\.P, t, rand\)\)/); - assert.match(CODE, /gl\.uniform3fv\(this\.locComp\.uHue, this\._wantAcc !== false/); + /* Renkler ayrı bir yöntemde: shader'lı yol uniform olarak tam renkle + (oran 1), sabit birleştirme yolu dosyadaki fShader oranıyla ve köşe + ağırlıklarına çevirerek aynı yöntemi kullanıyor (#580). */ + assert.match(CODE, /gl\.uniform3fv\(L\.hue_corner, this\._hueCorners\(1, t, rand\)\)/); + assert.match(CODE, /this\._hueCorners\(this\._fileVal\('fshader', 0\), this\.time, this\.randPreset\)/); // Dört köşe x üç bileşen assert.match(CODE, /new Float32Array\(12\)/); }); @@ -140,7 +144,7 @@ test('motor: köşe rengi en büyük bileşene bölünüp yeniden haritalanıyor /* Anahtar kapalıyken YAPI aynı kalıyor: yine dört köşe, yine aynı shader, yine aynı uniform. Yalnız dördüne de aynı renk gidiyor. */ test('motor: anahtar kapalıyken dört köşe de aynı rengi alıyor', () => { - const blk = /_hueCorners\(P, t, rand\) \{[\s\S]*?return hc;/.exec(CODE); + const blk = /_hueCorners\(amt, t, rand\) \{[\s\S]*?return hc;/.exec(CODE); assert.ok(blk, 'köşe rengi yöntemi bulunamadı'); assert.match(blk[0], /r = 0\.5 \+ 0\.5 \* Math\.sin\(t \* 0\.31\)/); assert.match(blk[0], /g = 0\.5 \+ 0\.5 \* Math\.sin\(t \* 0\.31 \+ 2\.09\)/); diff --git a/tests/milkdrop-hue-shader.test.js b/tests/milkdrop-hue-shader.test.js index 0501d3c..3cbcc1b 100644 --- a/tests/milkdrop-hue-shader.test.js +++ b/tests/milkdrop-hue-shader.test.js @@ -1,13 +1,21 @@ 'use strict'; -/* HUE_SHADER'IN MİKTARI: presetin `fShader` ayarı (#560). +/* DÖRT KÖŞE RENGİ VE `fShader` (#560, düzeltme #580). * - * MilkDrop dört köşe rengini hesapladıktan sonra beyaza doğru `fShader` - * oranıyla karıştırıyor, ve oran 0,001'in altındaysa rengi hiç - * hesaplamıyor — dört köşe de (1,1,1) kalıyor (milkdropfs.cpp:3857-3876). - * Varsayılan 0 (state.cpp:548). Motor rengi HER presete veriyordu. + * MilkDrop her kare dört köşe için ayrı fazlarla bir renk hesaplıyor ve iki + * yolda iki ayrı şekilde kullanıyor: + * - SHADER'LI YOL: `hue_shader`a HER ZAMAN tam renk gidiyor, `fShader` ne + * olursa olsun ("shader kullanıyor mu bilmiyoruz", milkdropfs.cpp:4122; + * BeatDrop'un D3D9 hâli 4318 aynı). `fShader` yalnız MilkDrop'un kendi + * yazdığı birleştirme metninde çarpan. + * - SABİT YOL: renk beyaza doğru presetin `fShader` oranıyla karışıyor ve + * oran 0,001'in altındaysa hiç hesaplanmıyor — köşeler beyaz + * (milkdropfs.cpp:3857-3884). Oran dosyadaki değer, geçişte doğrusal + * karışıyor (CBlendableFloat); kenetlenmiyor. * - * Korpusta comp shader'ında `hue_shader` okuyan 1.239 preset var (%12,0); - * bunların 914'ü `fShader`ı sıfır bırakıyor, 36'sı ara bir oran yazıyor. + * 52c7391 (#560) sabit yolun kuralını shader'lı yola da uyguluyordu: + * korpusta `hue_shader` okuyan 1.239 presetin 914'ü `fShader`ı sıfır + * bırakıyor ve rengini kaybediyordu, 36'sı rengin yalnız bir kısmını + * alıyordu. */ const test = require('node:test'); const assert = require('node:assert'); @@ -25,6 +33,10 @@ function method(sig) { } const body = (src) => src.slice(src.indexOf('{') + 1, src.lastIndexOf('}')); +// Köşe rengi yöntemi kaynaktan +const hueCorners = new Function('amt', 't', 'rand', body(method('_hueCorners(amt, t, rand)'))); +const fileVal = new Function('key', 'dflt', body(method('_fileVal(key, dflt)'))); + /* Motorun kendi uniform yükleyicisini koşturur ve `hue_corner`a yazılan on iki sayıyı döndürür. Yalnız o uniform'un konumu verildiği için ötekilerin hepsi `if (L[n])` denetiminden geçemiyor ve atlanıyor. */ @@ -49,13 +61,13 @@ function corners(opts) { _texSizeFor: () => [1, 1, 1, 1], _rotRows: () => new Float32Array(12), _blurScaleBias: () => [1, 0], - // köşe renkleri ayrı bir yöntemde; o da kaynaktan geliyor - _hueCorners: new Function('P', 't', 'rand', body(method('_hueCorners(P, t, rand)'))), + _hueCorners: hueCorners, blur: [], preset: null, }; const L = { hue_corner: 'HC', _plan: [], _texSize: [], _rot: [] }; - const P = { get: (k) => (k === 'fshader' ? opts.fshader : 0) }; + // Presetin hem dosyası hem havuzu fShader'ı taşıyor; shader yolu ikisine de bakmamalı + const P = { get: (k) => (k === 'fshader' ? opts.fshader : 0), file: { params: { fshader: opts.fshader } } }; const ctx = { w: 960, h: 720, aspectx: 1, aspecty: 0.75, aspX: 1, aspY: 0.75, time: opts.time === undefined ? 12.5 : opts.time, @@ -68,26 +80,18 @@ function corners(opts) { return got; } -test('fShader sıfırken dört köşe de beyaz', () => { - for (const v of [0, 0.0005, undefined, -1]) { - const hc = corners({ fshader: v }); - assert.deepStrictEqual(hc, new Array(12).fill(1), 'fShader = ' + v); - } -}); +// ------------------------------------------------------------ shader yolu -test('fShader 1 iken tam renk, ara değerde beyaza karışıyor', () => { +test('shader yolu: fShader ne olursa olsun tam renk', () => { const full = corners({ fshader: 1 }); - const half = corners({ fshader: 0.5 }); - assert.ok(full.some((v) => v < 0.999), 'tam oranda renk yok'); - for (let i = 0; i < 12; i++) { - // yarı oran: tam rengin beyazla ortası - assert.ok(Math.abs(half[i] - (full[i] * 0.5 + 0.5)) < 1e-6, 'köşe ' + i); - assert.ok(half[i] >= full[i] - 1e-9, 'yarı oran daha koyu çıktı'); + assert.ok(full.some((v) => v < 0.999), 'renk yok'); + for (const v of [0, 0.0005, undefined, -1, 0.5, 10]) { + assert.deepStrictEqual(corners({ fshader: v }), full, 'fShader = ' + v); } }); -test('köşeler birbirinden farklı ve en parlak bileşen 1 ile ölçekli', () => { - const hc = corners({ fshader: 1 }); +test('shader yolu: köşeler birbirinden farklı ve en parlak bileşen 1 ile ölçekli', () => { + const hc = corners({ fshader: 0 }); const c = [0, 1, 2, 3].map((i) => hc.slice(i * 3, i * 3 + 3)); assert.ok(c.some((v, i) => i > 0 && v.some((x, k) => Math.abs(x - c[0][k]) > 1e-6)), 'dört köşe aynı renk'); @@ -110,20 +114,59 @@ test('uyum kapalıyken eski tek renk duruyor', () => { } }); -/* SABİT birleştirme yolu da aynı renkleri uyguluyor: MilkDrop tam ekran - dörtgenini bu dört renkle çiziyor (milkdropfs.cpp:3940-3946). Motorda o - yol rengi hiç uygulamıyordu; comp shader'ı olmayan 2.129 presetin 631'i - sıfırdan büyük bir fShader yazıyor. */ -test('sabit birleştirme yolu köşe renklerini ekran boyunca uyguluyor', () => { +// -------------------------------------------------------------- sabit yol + +const hue = (amt) => Array.from(hueCorners.call({ _wantAcc: true, randPreset: [0.1, 0.2, 0.3, 0.4] }, + amt, 12.5, [0.1, 0.2, 0.3, 0.4])); + +test('sabit yolun oranı: 0,001 ve altı beyaz, ara değer beyaza karışıyor, 1\'in üstü kenetlenmiyor', () => { + for (const v of [0, 0.0005, 0.001, -1, NaN, undefined]) { + assert.deepStrictEqual(hue(v), new Array(12).fill(1), 'oran ' + v); + } + const full = hue(1); + const half = hue(0.5); + const ten = hue(10); + for (let i = 0; i < 12; i++) { + assert.ok(Math.abs(half[i] - (full[i] * 0.5 + 0.5)) < 1e-6, 'yarı oran, bileşen ' + i); + assert.ok(Math.abs(ten[i] - (full[i] * 10 - 9)) < 1e-4, 'oran 10, bileşen ' + i); + } + // Oran 10'da renk 0,5'in altına, eksiye iniyor — COLOR_NORM onu sarıyor + assert.ok(ten.some((v) => v < 0), 'oran 10 kenetlenmiş'); +}); + +test('sabit yolun oranı dosyadan: denklemin yazdığı fshader MilkDrop\'a ulaşmıyor', () => { + // Havuz 1 diyor (denklem yazmış), dosya 0,4 + const P = { get: () => 1, file: { params: { fshader: 0.4 } } }; + assert.strictEqual(fileVal.call({ preset: P, oldPreset: null, blendProg: 1 }, 'fshader', 0), 0.4); + const bos = { get: () => 1, file: { params: {} } }; + assert.strictEqual(fileVal.call({ preset: bos, oldPreset: null, blendProg: 1 }, 'fshader', 0), 0, + 'yazılmamışsa MilkDrop\'un varsayılanı'); +}); + +test('sabit yolun oranı geçişte eski ve yeni dosya değeri arasında doğrusal', () => { + const mk = (v) => ({ file: { params: { fshader: v } } }); + const self = { preset: mk(0), oldPreset: mk(1), blendProg: 0.25 }; + assert.ok(Math.abs(fileVal.call(self, 'fshader', 0) - 0.75) < 1e-12, 'kosinüs değil, ham ilerleme'); + self.blendProg = 1; + assert.strictEqual(fileVal.call(self, 'fshader', 0), 0, 'geçiş bitti'); + self.blendProg = 0.5; + self.oldPreset = { file: { params: {} } }; + assert.ok(Math.abs(fileVal.call(Object.assign({}, self, { preset: mk(1) }), 'fshader', 0) - 0.5) < 1e-12, + 'eski tarafta yazılmamış: varsayılandan'); +}); + +/* Sabit birleştirme yolu: ton rengi dosyadaki orandan, köşe ağırlıklarına + çevrilerek (çizim başına COLOR_NORM). Uyum kapalıyken renk hiç + hesaplanmıyor — eski sabit yol rengi uygulamıyordu. */ +test('sabit birleştirme yolu köşe rengini ağırlıklarla dörtgene uyguluyor', () => { const frag = /const COMP_FIXED_FRAG = `([\s\S]*?)`;/.exec(SRC)[1]; - assert.match(frag, /uniform vec3 uHue\[4\];/); - // Alt satır 2-3, üst satır 0-1: dörtgenin köşe sırası - assert.match(frag, /c \*= mix\(mix\(uHue\[2\], uHue\[3\], vUV\.x\), mix\(uHue\[0\], uHue\[1\], vUV\.x\), vUV\.y\);/); - // Gama çarpanından ÖNCE: MilkDrop rengi katmana uygulayıp sonra gama için - // katmanı yeniden çiziyor - assert.ok(frag.indexOf('uHue[2]') < frag.indexOf('c *= uGamma;'), 'renk gamadan sonra uygulanıyor'); + assert.match(frag, /uniform vec3 uWMain\[4\];/); + assert.match(frag, /uniform vec3 uWEcho\[4\];/); + assert.doesNotMatch(frag, /uHue/, 'eski tek çarpanlı renk kalmamalı'); const pass = method('_drawCompPass(gl, dst, prog, ctx)'); - assert.match(pass, /this\._hueCorners\(Pp, this\.time, this\.randPreset\)/); - assert.match(pass, /new Float32Array\(12\)\.fill\(1\)/); // uyum kapalıyken beyaz + const acc = /if \(acc\) \{([\s\S]*?)\} else \{([\s\S]*?)\}\s*gl\.uniform4f/.exec(pass); + assert.ok(acc, 'uyum dalları bulunamadı'); + assert.match(acc[1], /this\._hueCorners\(this\._fileVal\('fshader', 0\), this\.time, this\.randPreset\)/); + assert.doesNotMatch(acc[2], /_hueCorners/, 'uyum kapalıyken renk yok'); }); From e34ea84e66c36a18b2540a7a3cd2761870787d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87A=C4=9EAN=20TURGUT?= Date: Wed, 23 Sep 2026 17:26:23 +0300 Subject: [PATCH 2/2] docs: hue colour as MilkDrop gives it README (both languages): composite shaders get the hue colour MilkDrop gives them, and the fixed pipeline draws it through MilkDrop's passes. ROADMAP: the #560 hue_shader entry is corrected in place, since its first version read the fixed pipeline's rule as applying to shaders. The #580 entry records the fix and its measurements, including the one class flip on the 900-preset sample, checked alone. The remaining missing-key fallbacks come next, and the orientation of uv in preset shaders is listed as an open check. Refs #580, #560 --- README.md | 6 +++ README.tr.md | 6 +++ ROADMAP.md | 130 ++++++++++++++++++++++++++++++++++----------------- 3 files changed, 100 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index d75e135..eed8180 100644 --- a/README.md +++ b/README.md @@ -255,6 +255,12 @@ that asserts the bar profile has no step in it. flipping. A preset that leaves out its decay or gamma gets MilkDrop's 0.98 and 2.0 instead of 0, and so do most other values a preset leaves out; a rotation centre of 0 stays in the corner. Rendered through the engine, every case matches MilkDrop's formula within 2/255. +- **Composite shaders get the hue colour MilkDrop gives them.** MilkDrop passes every composite + shader four slowly drifting corner colours as `hue_shader`, whatever the preset's `fShader` + says; `fShader` only scales them on the fixed pipeline. The engine applied `fShader` to shaders + too, so about 950 presets of the corpus drew without their colour or with part of it, and the + corners were mirrored top to bottom. On the fixed pipeline the colour now goes through MilkDrop's + own draw passes, the wraparound of out-of-range colours included. - **Textured shapes, motion vectors, rotation matrices, mesh density, internal resolution scale, mouse input and preset transitions** are all implemented, and each sampler is read with the filtering and wrapping its name asks for (`sampler_pw_main` is point-sampled, `sampler_fc_main` is diff --git a/README.tr.md b/README.tr.md index 47b22e1..9d41ef1 100644 --- a/README.tr.md +++ b/README.tr.md @@ -248,6 +248,12 @@ profilinde basamak olmadığını doğruluyor. yeniden beliriyor. Sönmesini ya da gamasını yazmayan bir preset 0 yerine MilkDrop'un 0,98 ve 2,0'ını alıyor, yazmadığı öteki değerlerin çoğu da öyle; dönme merkezi 0 ise köşede kalıyor. Motordan geçirilerek çizildi: her durum MilkDrop'un formülüyle 2/255 içinde aynı. +- **Birleştirme shader'ları MilkDrop'un verdiği ton rengini alıyor.** MilkDrop her birleştirme + shader'ına `hue_shader` olarak dört köşede yavaşça gezinen renkler veriyor, presetin `fShader`ı + ne derse desin; `fShader` onları yalnız sabit yolda ölçekliyor. Motor `fShader`ı shader'lara da + uyguluyordu: korpustaki yaklaşık 950 preset renksiz ya da rengin bir kısmıyla çiziliyordu, + köşeler de dikeyde aynalanmıştı. Sabit yolda renk artık MilkDrop'un kendi çizim geçişlerinden, + aralık dışı renklerin sarması dahil, geçiyor. - **Dokulu şekiller, hareket vektörleri, dönme matrisleri, ağ sıklığı, iç çözünürlük, fare girdisi ve preset geçişleri** uygulandı; her sampler adının istediği süzme ve sarma ile okunuyor (`sampler_pw_main` noktasal, `sampler_fc_main` süzülmüş+kenetli — presetlerin %22,7'si aynı diff --git a/ROADMAP.md b/ROADMAP.md index 8dd77b5..65e7b6a 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -949,23 +949,24 @@ MilkDrop (#560): preset changes class, and rendered alone in both trees it is identical - that flip is the picture it inherits from the preset before it in the run, not this change. The legacy run is identical byte for byte. -- **`hue_shader` only colours what the preset asked to colour** · done on - `main`. MilkDrop mixes the four corner colours towards white by the - preset's `fShader` amount, and skips them entirely below 0.001, leaving - white (milkdropfs.cpp:3857-3876); the default is zero (state.cpp:548). We - handed every preset the full colour. Of the 1,239 presets (12.0%) whose - composite shader reads `hue_shader`, 914 leave `fShader` at zero - their - picture was being tinted by a colour MilkDrop never sends - and 36 ask for - a partial amount. Six of those presets rendered alone in both trees all - differ, five of them by a lot (means of 3.6 to 26.9 of 255, up to 89% of - pixels past 8), and all six are identical with the fidelity switch off; on - the 900-preset sample no preset changes class. The same four colours then - reached the other half of the picture: MilkDrop draws its fixed composite - quad with them as vertex colours (milkdropfs.cpp:3940-3946) and we applied - none, so the 631 presets that have no composite shader and ask for a - non-zero `fShader` were missing the tint entirely. Six of them rendered - alone all differ (means of 0.3 to 13.0 of 255, up to 60% of pixels past 8) - and stay identical with the switch off. +- **`hue_shader` gets the colour MilkDrop sends** · done on `main`, + corrected in #580. MilkDrop builds four corner colours every frame and uses + them in two ways. Every composite shader gets the full colour as + `hue_shader`, whatever the preset's `fShader` says - the D3D11 and the D3D9 + code both note "since we don't know if shader uses it or not" + (milkdropfs.cpp:4122) - and `fShader` scales it only in the shader MilkDrop + writes for a preset without one. The fixed composite mixes the colours + towards white by `fShader`, skips them below 0.001 (milkdropfs.cpp: + 3857-3884; the default is zero, state.cpp:548) and draws its quad with them + as vertex colours (3940-3946). The first version of this change (16 + September) took the fixed composite's rule for the shaders too: with + fidelity on, the 914 presets whose composite shader reads `hue_shader` and + leaves `fShader` at zero lost their colour, and 36 got part of it. #580 gave + the full colour back and found the shader corners mirrored top to bottom as + well. The fixed composite applied no colour at all before this change; the + 631 presets without a composite shader that ask for a non-zero `fShader` + got the tint here and kept it, and #580 takes its amount from the file + instead of per-frame values and draws it as MilkDrop does. - **Motion vectors sit where MilkDrop puts them and point where it points** · done on `main`. Four differences in one field (milkdropfs.cpp:1172-1320): the count is truncated with its fraction widening the grid spacing, and the @@ -2030,9 +2031,9 @@ rest after. No version number yet. was equivalent, and the branch it changed was removed. Putting back the old ring nouns, the " · " in mash-up names or removing the module's script tag each fails a test. -- **Fidelity follow-ups (#580)** · the fixed composite, the stage rule and - MilkDrop's defaults are done; the other audits and the file-reading - differences are next. +- **Fidelity follow-ups (#580)** · the fixed composite, the stage rule, + MilkDrop's defaults and the hue colour are done; the remaining defaults, + the other audits and the file-reading differences are next. - **Checked against the source.** Nullsoft's own code (jecassis/foo_vis_milk2 5b44cea) and, for the fixed pipeline's blend passes, the D3D9 code the D3D11 fork was ported from (BeatDrop @@ -2101,25 +2102,59 @@ rest after. No version number yet. preset with `PSVERSION_WARP=0` was written as version 2 and drawn — so the mash-up rule version went from 1 to 2: a mash-up saved under the old rule keeps its file, and saving the recipe again writes a new one. - - **Found while finishing this, fixed next.** MilkDrop hands every - composite shader the full hue colour ("since we don't know if shader - uses it or not", in both the D3D11 and the D3D9 code); `fShader` scales - it only on the fixed path and in the shader MilkDrop writes for a - preset without one. The engine has applied `fShader` to preset shaders - too since the `hue_shader` change of 16 September, so with fidelity on - the 914 corpus presets that read `hue_shader` and leave `fShader` at 0 - lost their colour, and 36 got part of it. In the shader written for a - composite stage with a version and no text, a partial `fShader` now - lands twice, in the text and in the colour; the one corpus preset with - such a stage has `fShader` at 0. The engine also takes the - amount from per-frame values: 16 presets write `fshader` in code, - which MilkDrop never sees, all of them with a composite shader. Some - keys fall back to something else when a file leaves them out. MilkDrop - reads a missing `wave_r`, `wave_g`, `wave_b`, `wave_x` or `wave_y` as - `rot`'s value at that point, which is 0, where the engine has 1, 1, 1, - 0.5 and 0.5. Missing wave smoothing is 0.75, and the volume fade runs - from 0.75 to 0.95. Custom shapes default to red inside and green - outside. No corpus preset leaves any of these out, and neither do ours. + - **The hue colour, as MilkDrop gives it** (found while finishing the + first change). MilkDrop hands every composite shader the full hue + colour, and the engine had applied `fShader` there as well (see the + corrected #560 entry). With fidelity on, preset shaders get the full + colour again. Their corners were mirrored top to bottom too: MilkDrop + weighs them by the composite grid's screen position, with y at 1 on top + (plugin.cpp:1475-1490), and our translation flipped y. On the fixed + composite the amount is the file's `fShader`, blended linearly through a + transition like MilkDrop's other file values; 16 presets write `fshader` + in per-frame code, which MilkDrop never sees. It is no longer clamped to + 1, and each draw's corner colour goes through `COLOR_NORM`, so the 16 + presets with `fShader` 10 wrap into other colours as in MilkDrop. The + quad is interpolated as MilkDrop's two triangles, not bilinearly, and a + generated composite stage no longer gets a partial `fShader` twice. Echo + fading during a blend now looks at the new side's blended echo alpha, as + MilkDrop does, so the old echo fades out even when the new preset has + none. + - **Measured.** Four pure colours read back from the screen: corner 0 + sits top right in shaders and top left on the fixed quad, as in + MilkDrop, and the quad's centre is the mean of corners 1 and 2. The + fixed composite through the engine: 72 of 72 cases within 2/255, + including gamma 3.7 with echo 0.5, which is eight draws; a simulation + that rounds after every draw, as MilkDrop's 8-bit buffer does, differs + from our single rounding by at most 0.6/255 in those cases. The last + ten of 60 frames against `main`, up to 120 presets a group: + - shader presets that do not read `hue_shader`: 120 of 120 identical; + - `fShader` at 0: 95 of 120 changed by more than 1%, and their median + saturation went from 0.006 to 0.150; + - `fShader` 1 or more, where only the corners move: 90 of 120 changed, + median 4.3%; + - partial `fShader`: 28 of 36 changed; + - fixed composite with `fShader` above 0.001: 30 of 120 changed, + median 0.5%, and 12 of the 16 at 10; + - fixed composite without it: rounding only, the largest mean change + 0.36%. + + On the 900-preset sample one preset moves from blown to clean (882 → + 883 clean): it scales its picture by `hue_shader`, which was white on + `main`; rendered alone its brightness goes from 0.555 to 0.507 and its + share of pixels at full from 45.8% to 41.7%. + With fidelity off the fixed composite and the control group stay + byte-identical; presets that read `hue_shader` differ in at most 0.1% + of pixels by one level, the same four equal corner colours summed in + another order. + - **Next: keys that fall back to other values.** MilkDrop reads a + missing `wave_r`, `wave_g`, `wave_b`, `wave_x` or `wave_y` as `rot`'s + value at that point, which is 0, where the engine has 1, 1, 1, 0.5 and + 0.5. Missing wave smoothing is 0.75, and the volume fade runs from 0.75 + to 0.95. Custom shapes default to red inside and green outside. MilkDrop + also blends its other file-only values linearly through a transition + (wave scale and smoothing, the fade range, warp scale), where the engine + reads them from per-frame values. No corpus preset leaves any of these + keys out, and neither do ours. - **Not done yet:** the fixed warp path, the blur chain, borders and centre darkening, and the rest of the blend snap points; the file-reading differences found while building the mash-ups (a @@ -2127,8 +2162,12 @@ rest after. No version number yet. the last; numbered code that MilkDrop stops reading at the first missing number; `\\` comments; integer keys holding fractions; text after a number; key case; indented lines — about 40 corpus presets in - all); the reference comparison with an external renderer, which needs - one installed and waits for the user's approval. + all); whether `uv` in preset shaders runs the way MilkDrop's does — + read back from the screen, our `uv.y` is 1 at the top, where MilkDrop's + texture coordinate is 0; sampling agrees, but a shader doing arithmetic + on `uv.y` may come out mirrored, which needs its own check; the + reference comparison with an external renderer, which needs one + installed and waits for the user's approval. - **Tests.** 14 new: the version rule and the stage choice, the two generated shaders (float rounding, samplers, echo, hue, flag order, and that they translate), echo orientation and gamma cases, the @@ -2144,7 +2183,14 @@ rest after. No version number yet. check follow the new key; the mash-up tests check the part test against the engine's stage choice and expect `md_mix2_` ids. 36 of 36 mutations are caught, one of them putting the mash-up rule version - back to 1. + back to 1. The hue change adds three tests — the fixed quad's two + triangles run through the shader's own formula, the fixed amount taken + from the file and not from per-frame values, and its linear blend — and + reworks the hue tests: preset shaders at full colour for every + `fShader`; the fixed amount's threshold, partial mix and no clamp; the + draw weights (pass counts, the 1.0001 cut, the edge below −0.999, + wraparound, per-corner order); the echo fade with no echo on the new + side; and `hueAt` not flipping y. 18 of 18 mutations are caught. ## v3.1.6 — Comprehensive video export