Ambient: Drift v4 – Bach klingt nach Bach (glucksende Bläschengruppen), Bedienfeld auf breiten Fenstern zentriert; sw v76
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 6m52s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mrwnslz 2026-10-03 23:41:48 +02:00
parent 0fbb948c1a
commit 3ff998f9cf
2 changed files with 31 additions and 25 deletions

View file

@ -20,7 +20,7 @@
.noise .m-music, .noise .meters { display:none !important; } .noise .m-music, .noise .meters { display:none !important; }
html:not(.noise) .m-noise { display:none; } html:not(.noise) .m-noise { display:none; }
.noise:not(.rev) .m-hall { display:none; } .noise:not(.rev) .m-hall { display:none; }
main > * { pointer-events:auto; } main > * { pointer-events:auto; width:100%; max-width:900px; margin-inline:auto; } /* auf breiten Fenstern zentriert statt über die ganze Breite */
h1 { margin:0; font-weight:300; letter-spacing:.35em; text-transform:uppercase; font-size:18px; } h1 { margin:0; font-weight:300; letter-spacing:.35em; text-transform:uppercase; font-size:18px; }
.sub { color:var(--dim); font-size:13px; margin-top:4px; } .sub { color:var(--dim); font-size:13px; margin-top:4px; }
.state { font-size:13px; color:var(--dim); font-variant-numeric:tabular-nums; } .state { font-size:13px; color:var(--dim); font-variant-numeric:tabular-nums; }
@ -670,28 +670,31 @@ function dropBuf(kind, rate) {
return DB[key] = buf; return DB[key] = buf;
} }
// Wasser: tausende Luftbläschen als gedämpfte, leicht ansteigende Sinus-Schwingungen nach dem Minnaert-Modell // Wasser: Luftbläschen als gedämpfte, ansteigende Sinus-Schwingungen (Minnaert-Modell: Frequenz ≈ 3 / Radius,
// (Frequenz ≈ 3 / Radius, Dämpfung wächst mit der Frequenz). Dichte, überlagerte Bläschen ergeben das Plätschern; // Dämpfung wächst mit der Frequenz). Ein Bach "gluckst" in Gruppen: ähnlich große Bläschen platzen kurz hintereinander,
// die Ereignisrate schwankt periodisch über die 10 s, damit der Bach "atmet". // jede Gruppe hat ihre eigene Lautstärke, und die Gruppenrate schwankt periodisch über die 10 s.
let SIN = null; let SIN = null;
function fillWater(d, len, sr, rate) { function fillWater(d, len, sr, rate) { // rate = Gruppen pro Sekunde
if (!SIN) { SIN = new Float32Array(4096); for (let i = 0; i < 4096; i++) SIN[i] = Math.sin(i / 4096 * 2 * Math.PI); } if (!SIN) { SIN = new Float32Array(4096); for (let i = 0; i < 4096; i++) SIN[i] = Math.sin(i / 4096 * 2 * Math.PI); }
const N = Math.round(rate * 10), p1 = Math.random() * 6.28, p2 = Math.random() * 6.28, p3 = Math.random() * 6.28; const NC = Math.round(rate * 10), p1 = Math.random() * 6.28, p2 = Math.random() * 6.28, p3 = Math.random() * 6.28;
for (let e = 0; e < N; e++) { for (let c = 0; c < NC; c++) {
let pos; let pos;
do { do {
pos = Math.random(); pos = Math.random();
var lam = 1 + .45 * Math.sin(2 * Math.PI * 3 * pos + p1) + .3 * Math.sin(2 * Math.PI * 7 * pos + p2) + .2 * Math.sin(2 * Math.PI * 11 * pos + p3); var lam = 1 + .45 * Math.sin(2 * Math.PI * 3 * pos + p1) + .3 * Math.sin(2 * Math.PI * 7 * pos + p2) + .2 * Math.sin(2 * Math.PI * 11 * pos + p3);
} while (Math.random() * 1.95 > lam); } while (Math.random() * 1.95 > lam);
const r = (0.7 + 7.3 * Math.pow(Math.random(), 2.2)) * 1e-3; // Radius 0,7–8 mm, kleine häufiger const rc = (1 + 4 * Math.pow(Math.random(), 1.5)) * 1e-3; // typischer Radius der Gruppe: 1–5 mm
const f0 = Math.min(4500, 3 / r), dmp = .043 * f0 + .0014 * Math.pow(f0, 1.5), xi = .1 * dmp; const loud = Math.pow(Math.random(), 2) * 1.2 + .05, m = 2 + Math.floor(Math.random() * 7), span = (.04 + Math.random() * .16) * sr;
const amp = Math.pow(r / .004, 1.3) * (.3 + Math.random()); for (let b = 0; b < m; b++) {
const n = Math.min(Math.floor(sr * 3.5 / dmp), 4000), start = Math.floor(pos * len), dec = Math.exp(-dmp / sr), att = sr * .0008; const r = rc * (1 + (Math.random() - .5) * .3), f0 = 3 / r, dmp = .043 * f0 + .0014 * Math.pow(f0, 1.5), xi = .3 * dmp; // kräftiger Anstieg = Glucksen
let env = 1, phase = 0; const amp = loud * Math.pow(r / .003, 1.2) * (.5 + Math.random() * .5);
for (let j = 0; j < n; j++) { const n = Math.min(Math.floor(sr * 3.5 / dmp), 5000), start = Math.floor(pos * len + Math.random() * span), dec = Math.exp(-dmp / sr), att = sr * .0008;
phase += f0 * (1 + xi * j / sr) / sr; let env = 1, phase = 0;
d[(start + j) % len] += SIN[((phase - Math.floor(phase)) * 4096) | 0] * env * amp * (j < att ? j / att : 1); for (let j = 0; j < n; j++) {
env *= dec; phase += f0 * (1 + xi * j / sr) / sr;
d[(start + j) % len] += SIN[((phase - Math.floor(phase)) * 4096) | 0] * env * amp * (j < att ? j / att : 1);
env *= dec;
}
} }
} }
} }
@ -812,17 +815,20 @@ function buildWind(K) {
} }
function buildStream(K) { function buildStream(K) {
const ctx = S.ctx, g1 = ctx.createGain(), g2 = ctx.createGain(), hp = bq("highpass", 220, .5); const ctx = S.ctx, g1 = ctx.createGain(), g2 = ctx.createGain(), hp = bq("highpass", 250, .5);
const s1 = K.src(dropBuf("water", 350), g1), s2 = K.src(dropBuf("water", 1300), g2); const s1 = K.src(dropBuf("water", 12), g1), s2 = K.src(dropBuf("water", 45), g2); // ruhig / sprudelnd
g1.connect(hp); g2.connect(hp); g1.connect(hp); g2.connect(hp);
const fl = bq("bandpass", 1200, .5), fg = ctx.createGain(); K.src(noiseBuf("pink"), fl); fl.connect(fg).connect(hp); // verschmiert die Bläschen zu einem Fluss const sp = bq("highpass", 1800, .5), spl = bq("lowpass", 5000, .5), spg = ctx.createGain(); // Spritzer
const low = bq("lowpass", 220, .5), lg = ctx.createGain(); lg.gain.value = .12; K.src(noiseBuf("brown"), low); low.connect(lg).connect(K.out); K.src(dropBuf("rain", 30), sp); sp.connect(spl).connect(spg).connect(K.out);
const fl = bq("bandpass", 700, .7), fg = ctx.createGain(); // ganz leises, unregelmäßiges Rauschbett
fg.gain.value = .03; K.src(noiseBuf("pink"), fl); fl.connect(fg).connect(hp);
const low = bq("lowpass", 180, .5), lg = ctx.createGain(); lg.gain.value = .05; K.src(noiseBuf("brown"), low); low.connect(lg).connect(K.out);
hp.connect(K.out); hp.connect(K.out);
K.set = (a, b) => { K.set = (a, b) => {
sm(g1.gain, 1 - a * .8); sm(g2.gain, a * 1.2); sm(fg.gain, .04 + .12 * a); sm(g1.gain, 1 - a * .7); sm(g2.gain, a * 1.3); sm(spg.gain, .02 + .07 * a);
const rt = .75 + .65 * b; sm(s1.playbackRate, rt); sm(s2.playbackRate, rt * 1.03); const rt = .8 + .5 * b; sm(s1.playbackRate, rt); sm(s2.playbackRate, rt * 1.03);
}; };
K.every(() => sm(fl.frequency, R(800, 2400), 2), 2, 5); K.every(() => { sm(fl.frequency, R(500, 1800), .4); sm(fg.gain, R(.01, .07) * (.4 + K.a), .2); }, .4, 1.2);
} }
function buildFire(K) { function buildFire(K) {
@ -965,7 +971,7 @@ const MODES = {
storm: { group: "Natur", name: "Gewitter", desc: "Starker Regen mit Blitz und rollendem Donner in unregelmäßigen Abständen.", labels: ["Häufigkeit & Regen", "Entfernung (fern ↔ nah)"], def: [.6, .5], gain: .8, hue: 245, vis: "rain", rev: .5, build: K => buildRain(K, true) }, storm: { group: "Natur", name: "Gewitter", desc: "Starker Regen mit Blitz und rollendem Donner in unregelmäßigen Abständen.", labels: ["Häufigkeit & Regen", "Entfernung (fern ↔ nah)"], def: [.6, .5], gain: .8, hue: 245, vis: "rain", rev: .5, build: K => buildRain(K, true) },
ocean: { group: "Natur", name: "Meer", desc: "Wellen, die anrollen, brechen und im Schaum auslaufen.", labels: ["Wellengang", "Brandung dumpf ↔ hell"], def: [.5, .5], gain: .9, hue: 195, vis: "float", build: buildOcean }, ocean: { group: "Natur", name: "Meer", desc: "Wellen, die anrollen, brechen und im Schaum auslaufen.", labels: ["Wellengang", "Brandung dumpf ↔ hell"], def: [.5, .5], gain: .9, hue: 195, vis: "float", build: buildOcean },
wind: { group: "Natur", name: "Wind", desc: "Böen mit wandernder Farbe, auf Wunsch mit feinem Pfeifen.", labels: ["Stärke", "Pfeifen"], def: [.5, .25], gain: .9, hue: 180, vis: "wind", build: buildWind }, wind: { group: "Natur", name: "Wind", desc: "Böen mit wandernder Farbe, auf Wunsch mit feinem Pfeifen.", labels: ["Stärke", "Pfeifen"], def: [.5, .25], gain: .9, hue: 180, vis: "wind", build: buildWind },
stream: { group: "Natur", name: "Bach", desc: "Plätscherndes Wasser aus tausenden einzeln berechneten Luftbläschen.", labels: ["Strömung", "Tonhöhe"], def: [.5, .5], gain: 0.5, hue: 170, vis: "float", build: buildStream }, stream: { group: "Natur", name: "Bach", desc: "Gluckerndes, plätscherndes Wasser aus Gruppen einzeln berechneter Luftbläschen.", labels: ["Strömung", "Tonhöhe"], def: [.5, .5], gain: 0.5, hue: 170, vis: "float", build: buildStream },
fire: { group: "Natur", name: "Lagerfeuer", desc: "Rollendes Brummen der Flamme und Knistern.", labels: ["Flammenstärke", "Knistern selten ↔ dicht"], def: [.5, .45], gain: .8, hue: 25, vis: "rise", build: buildFire }, fire: { group: "Natur", name: "Lagerfeuer", desc: "Rollendes Brummen der Flamme und Knistern.", labels: ["Flammenstärke", "Knistern selten ↔ dicht"], def: [.5, .45], gain: .8, hue: 25, vis: "rise", build: buildFire },
crickets: { group: "Natur", name: "Sommernacht", desc: "Bis zu sechs Grillen, jede mit eigenem Rhythmus und eigener Tonhöhe.", labels: ["Anzahl", "Tonhöhe / Tempo"], def: [.5, .45], gain: 0.2, hue: 75, vis: "fly", build: buildCrickets }, crickets: { group: "Natur", name: "Sommernacht", desc: "Bis zu sechs Grillen, jede mit eigenem Rhythmus und eigener Tonhöhe.", labels: ["Anzahl", "Tonhöhe / Tempo"], def: [.5, .45], gain: 0.2, hue: 75, vis: "fly", build: buildCrickets },

View file

@ -2,7 +2,7 @@
* diora service worker — caches the app shell for offline use. * diora service worker — caches the app shell for offline use.
*/ */
const CACHE = 'diora-v75'; const CACHE = 'diora-v76';
const SHELL = [ const SHELL = [
'/static/css/app.css', '/static/css/app.css',
'/static/js/app.js', '/static/js/app.js',