Ambient: Drift v4 – Bach klingt nach Bach (glucksende Bläschengruppen), Bedienfeld auf breiten Fenstern zentriert; sw v76
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0fbb948c1a
commit
3ff998f9cf
2 changed files with 31 additions and 25 deletions
|
|
@ -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 },
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue