Ambient: leichter Modus für Telefone gegen Ruckeln; sw v79
Auf dem S21 lag die App mit Drift bei ~185 % CPU, der Ton stockte. Auf Telefonen jetzt: Bild in einfacher Auflösung und ~30 Bilder/s, 3,5 s statt 7 s Hall, kein Oversampling, kein ScriptProcessor (Körner) im Hauptthread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
bd9193c869
commit
2c64342c53
2 changed files with 30 additions and 19 deletions
|
|
@ -97,6 +97,10 @@
|
|||
<script>
|
||||
"use strict";
|
||||
const $ = id => document.getElementById(id);
|
||||
// Telefone: leichter rechnen, damit der Ton nicht ruckelt (Bild in einfacher Auflösung, ~30 Bilder/s,
|
||||
// kürzerer Hall, kein Oversampling, keine Körner-Aufnahme im Hauptthread).
|
||||
const LITE = /Android|iPhone|iPad|Mobile/i.test(navigator.userAgent);
|
||||
const DPR = () => LITE ? 1 : devicePixelRatio;
|
||||
const QS = new URLSearchParams(location.search);
|
||||
if (QS.get("embed") === "1") document.documentElement.classList.add("embed");
|
||||
let BG = [11, 14, 20]; // Hintergrundfarbe, per ?bg=%23101010 überschreibbar
|
||||
|
|
@ -235,7 +239,7 @@ function buildGraph() {
|
|||
// gemeinsamer Summenbus -> Tape-Sättigung -> Kompressor
|
||||
g.mix = ctx.createGain();
|
||||
g.drive = ctx.createGain(); g.drive.gain.value = 1.5;
|
||||
g.shaper = ctx.createWaveShaper(); g.shaper.curve = satCurve(1.6); g.shaper.oversample = "2x";
|
||||
g.shaper = ctx.createWaveShaper(); g.shaper.curve = satCurve(1.6); g.shaper.oversample = LITE ? "none" : "2x";
|
||||
g.makeup = ctx.createGain(); g.makeup.gain.value = .72;
|
||||
g.comp = ctx.createDynamicsCompressor();
|
||||
g.comp.threshold.value = -18; g.comp.ratio.value = 3; g.comp.attack.value = .05; g.comp.release.value = .4;
|
||||
|
|
@ -246,7 +250,7 @@ function buildGraph() {
|
|||
g.filter.connect(g.dry).connect(g.mix);
|
||||
|
||||
// Hall
|
||||
g.conv = ctx.createConvolver(); g.conv.buffer = makeImpulse(ctx, 7, 2.6);
|
||||
g.conv = ctx.createConvolver(); g.conv.buffer = makeImpulse(ctx, LITE ? 3.5 : 7, 2.6);
|
||||
g.send = ctx.createGain(); g.send.gain.value = S.verb;
|
||||
g.filter.connect(g.send).connect(g.conv);
|
||||
g.conv.connect(g.mix);
|
||||
|
|
@ -294,8 +298,11 @@ function buildGraph() {
|
|||
g.noiseBus.connect(g.mix);
|
||||
const nSend = ctx.createGain(); nSend.gain.value = .3; g.noiseBus.connect(nSend).connect(g.conv);
|
||||
|
||||
// Granular: Ringpuffer der Stimmen, daraus werden Körner geschnitten
|
||||
S.ring = new Float32Array(ctx.sampleRate * 12); S.wp = 0; S.written = 0;
|
||||
// Granular: Ringpuffer der Stimmen, daraus werden Körner geschnitten. Der ScriptProcessor läuft im
|
||||
// Hauptthread und hält bei Last den ganzen Graphen auf – auf Telefonen gibt es daher keine Körner.
|
||||
S.wp = 0; S.written = 0;
|
||||
if (!LITE) {
|
||||
S.ring = new Float32Array(ctx.sampleRate * 12);
|
||||
const proc = ctx.createScriptProcessor(4096, 1, 1);
|
||||
proc.onaudioprocess = e => {
|
||||
const inp = e.inputBuffer.getChannelData(0), n = S.ring.length;
|
||||
|
|
@ -304,6 +311,7 @@ function buildGraph() {
|
|||
};
|
||||
const zero = ctx.createGain(); zero.gain.value = 0;
|
||||
g.voiceBus.connect(proc); proc.connect(zero).connect(ctx.destination);
|
||||
}
|
||||
g.grainBus = ctx.createGain(); g.grainBus.connect(g.filter);
|
||||
|
||||
g.modeBus = ctx.createGain(); g.modeBus.connect(g.mix); // Klangwelten (Rauschen, Natur, …)
|
||||
|
|
@ -464,7 +472,7 @@ function playSwell(buf, t) {
|
|||
// Granulare Wolke aus dem eigenen Output
|
||||
function snapGrains() {
|
||||
const sr = S.ctx.sampleRate;
|
||||
if (S.written < sr * 6) return;
|
||||
if (!S.ring || S.written < sr * 6) return;
|
||||
const len = Math.min(S.written, S.ring.length), b = S.ctx.createBuffer(1, len, sr), d = b.getChannelData(0);
|
||||
for (let i = 0; i < len; i++) d[i] = S.ring[(S.wp - len + i + S.ring.length) % S.ring.length];
|
||||
S.grainBuf = b;
|
||||
|
|
@ -1189,8 +1197,8 @@ const cv = $("viz"), cx = cv.getContext("2d");
|
|||
S.visUntil = performance.now() + 1500;
|
||||
function wake() { if (!rafId) rafId = requestAnimationFrame(draw); }
|
||||
function resize() {
|
||||
cv.width = Math.round(cv.clientWidth * devicePixelRatio);
|
||||
cv.height = Math.round(cv.clientHeight * devicePixelRatio);
|
||||
cv.width = Math.round(cv.clientWidth * DPR());
|
||||
cv.height = Math.round(cv.clientHeight * DPR());
|
||||
wake();
|
||||
}
|
||||
resize();
|
||||
|
|
@ -1204,9 +1212,9 @@ function spawnParticles(ev) {
|
|||
const keyHue = (S.root % 12) * 30 + 200;
|
||||
for (let i = 0; i < count && parts.length < 450; i++) {
|
||||
parts.push({
|
||||
x: x + rnd(-40, 40, Math.random) * devicePixelRatio, y: H * rnd(.55, .85, Math.random),
|
||||
x: x + rnd(-40, 40, Math.random) * DPR(), y: H * rnd(.55, .85, Math.random),
|
||||
vx: rnd(-.12, .12, Math.random), vy: -rnd(.08, .4, Math.random) * (ev.kind === "bell" ? 1.6 : 1),
|
||||
r: (ev.kind === "bell" ? rnd(4, 9, Math.random) : rnd(10, 30, Math.random)) * devicePixelRatio,
|
||||
r: (ev.kind === "bell" ? rnd(4, 9, Math.random) : rnd(10, 30, Math.random)) * DPR(),
|
||||
life: 0, max: ev.kind === "bell" ? rnd(180, 320, Math.random) : Math.min(ev.dur * 40, 700),
|
||||
h: keyHue + (ev.kind === "choir" ? 60 : ev.kind === "bell" ? 25 : 0) + rnd(-12, 12, Math.random),
|
||||
});
|
||||
|
|
@ -1235,13 +1243,16 @@ function modeVis(W, H, dpr) {
|
|||
if (n(.06 + a * .05)) add({ x: Math.random() * W, y: H * r(.5, .95), vx: r(-.2, .2), vy: -r(.1, .5), r: r(10, 28) * dpr, max: r(300, 600) });
|
||||
}
|
||||
}
|
||||
let lastDraw = 0;
|
||||
function draw(ts) {
|
||||
rafId = 0;
|
||||
const W = cv.width, H = cv.height, dpr = devicePixelRatio;
|
||||
const W = cv.width, H = cv.height, dpr = DPR();
|
||||
// Nichts zeichnen (und nicht neu planen), wenn versteckt, 0 px groß oder die Musik steht
|
||||
if (document.hidden || !W || !H || !cv.clientWidth) return;
|
||||
if (!S.running && performance.now() > S.visUntil) return;
|
||||
rafId = requestAnimationFrame(draw);
|
||||
if (LITE && ts - lastDraw < 32) return; // Telefone: ~30 Bilder/s reichen
|
||||
lastDraw = ts;
|
||||
cx.globalCompositeOperation = "source-over";
|
||||
cx.fillStyle = `rgba(${BG[0]},${BG[1]},${BG[2]},.16)`; cx.fillRect(0, 0, W, H);
|
||||
if (!S.g) return;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* diora service worker — caches the app shell for offline use.
|
||||
*/
|
||||
|
||||
const CACHE = 'diora-v78';
|
||||
const CACHE = 'diora-v79';
|
||||
const SHELL = [
|
||||
'/static/css/app.css',
|
||||
'/static/js/app.js',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue