2026-10-03 23:08:57 +02:00
<!doctype html>
< html lang = "de" >
< head >
< meta charset = "utf-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
< title > Drift – Ambient Synth< / title >
< style >
:root { --bg:#0b0e14; --fg:#d7dde8; --dim:#7b8598; --acc:#7fb4ff; --acc2:#c99bff; }
* { box-sizing:border-box; }
html,body { margin:0; height:100%; background:var(--bg); color:var(--fg);
font:15px/1.5 system-ui,-apple-system,"Segoe UI",sans-serif; overflow:hidden; }
body { position:relative; }
canvas { position:absolute; inset:0; width:100%; height:100%; display:block; }
main { position:relative; z-index:1; height:100%; display:flex; flex-direction:column; gap:18px;
justify-content:space-between; padding:28px 24px; pointer-events:none;
overflow-x:hidden; overflow-y:auto; }
.embed header { display:none; }
.embed #share { display:none; }
2026-10-04 00:01:03 +02:00
.embed .m-vol { display:none; } /* eingebettet regelt die Seite die Lautstärke (cmd "volume") */
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
.embed .state { padding-right:58px; } /* oben rechts liegt in diora ein Knopf */
.noise .m-music, .noise .meters { display:none !important; }
html:not(.noise) .m-noise { display:none; }
.noise:not(.rev) .m-hall { display:none; }
2026-10-04 00:07:07 +02:00
main > * { pointer-events:auto; width:100%; max-width:960px; margin-inline:auto; } /* auf breiten Fenstern zentriert statt über die ganze Breite */
2026-10-03 23:08:57 +02:00
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; }
.state { font-size:13px; color:var(--dim); font-variant-numeric:tabular-nums; }
.state b { color:var(--fg); font-weight:500; }
.state .big { font-size:34px; font-weight:200; color:var(--fg); display:block; letter-spacing:.04em; }
.panel { display:flex; flex-wrap:wrap; gap:14px 24px; align-items:end;
background:rgba(11,14,20,.6); backdrop-filter:blur(10px); border:1px solid #ffffff14;
border-radius:14px; padding:14px 18px; }
label { display:flex; flex-direction:column; gap:6px; font-size:11px; color:var(--dim);
text-transform:uppercase; letter-spacing:.12em; }
input[type=range] { width:130px; accent-color:var(--acc); }
select, input[type=text] { font:inherit; font-size:14px; color:var(--fg); background:#ffffff0f;
border:1px solid #ffffff22; border-radius:8px; padding:6px 10px; }
input[type=text] { width:120px; }
select option { background:#141925; }
button { font:inherit; color:var(--fg); background:#ffffff0f; border:1px solid #ffffff22;
border-radius:999px; padding:8px 18px; cursor:pointer; transition:.2s; }
button:hover { background:#ffffff1c; }
button.primary { background:linear-gradient(135deg,var(--acc),var(--acc2)); color:#0b0e14;
border:0; font-weight:600; min-width:100px; }
.sep { flex-basis:100%; height:0; }
.meters { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); max-width:340px; gap:6px 12px; margin-top:10px; }
.meter { height:3px; background:#ffffff18; border-radius:2px; overflow:hidden; }
.meter i { display:block; height:100%; width:50%; background:var(--acc); transition:width 1s linear; }
@media (max-width:520px) {
main { padding:18px 16px; }
.state .big { font-size:28px; }
.panel { display:grid; grid-template-columns:1fr 1fr; gap:12px 14px; padding:12px 14px; }
.panel .sep { display:none; }
.panel > button.primary { grid-column:1 / -1; }
.panel label, .panel select, .panel input[type=text], .panel input[type=range] { width:100%; min-width:0; }
}
.mlabel { font-size:10px; text-transform:uppercase; letter-spacing:.1em; color:var(--dim); }
< / style >
< / head >
< body >
< canvas id = "viz" > < / canvas >
< main >
< header >
< h1 > Drift< / h1 >
< div class = "sub" > Generativer Ambient-Synthesizer · alles wird live im Browser erzeugt< / div >
< / header >
< div class = "state" >
< span class = "big" id = "key" > – < / span >
< span id = "chord" > Noch still.< / span >
< div class = "meters" id = "meters" > < / div >
< / div >
< div class = "panel" >
< button class = "primary" id = "toggle" > Start< / button >
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
< label > Modus
< select id = "mode" > < / select > < / label >
< label class = "m-music" > Stimmung
2026-10-03 23:08:57 +02:00
< select id = "preset" > < / select > < / label >
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
< label class = "m-music" > Seed
2026-10-03 23:08:57 +02:00
< input type = "text" id = "seed" spellcheck = "false" > < / label >
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
< button id = "reroll" class = "m-music" title = "Neuer Seed, neue Szene" > Neu würfeln< / button >
< button id = "share" class = "m-music" title = "Link mit Seed und Stimmung kopieren" > Link kopieren< / button >
< label class = "m-music" > Tuning
2026-10-03 23:08:57 +02:00
< select id = "tuning" > < option value = "just" > Rein< / option > < option value = "equal" > Gleichstufig< / option > < / select > < / label >
< div class = "sep" > < / div >
2026-10-04 00:01:03 +02:00
< label class = "m-vol" > Lautstärke < input type = "range" id = "vol" min = "0" max = "1" step = "0.01" value = "0.7" > < / label >
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
< label class = "m-noise" > < span id = "lla" > A< / span > < input type = "range" id = "la" min = "0" max = "1" step = "0.01" value = "0.5" > < / label >
< label class = "m-noise" > < span id = "llb" > B< / span > < input type = "range" id = "lb" min = "0" max = "1" step = "0.01" value = "0.5" > < / label >
< label class = "m-music" > Dichte < input type = "range" id = "density" min = "0" max = "1" step = "0.01" value = "0.5" > < / label >
< label > < span id = "driftL" > Wandel-Tempo< / span > < input type = "range" id = "drift" min = "0" max = "1" step = "0.01" value = "0.5" > < / label >
< label class = "m-hall" > Hall < input type = "range" id = "verb" min = "0" max = "1" step = "0.01" value = "0.6" > < / label >
< label class = "m-music" > Spannungsbogen < input type = "range" id = "arc" min = "0" max = "1" step = "0.01" value = "0.6" > < / label >
2026-10-03 23:08:57 +02:00
< / div >
< / main >
< script >
"use strict";
const $ = id => document.getElementById(id);
2026-10-04 00:09:28 +02:00
// 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);
2026-10-04 10:42:45 +02:00
const DPR = () => LITE ? .5 : devicePixelRatio; // Telefone: das Bild in halber Auflösung, hochskaliert
const MAX_VOICES = 10; // Telefone: höchstens so viele Stimmen zugleich (der Grundton zählt nicht)
2026-10-03 23:08:57 +02:00
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
{
const m = /^#?([0-9a-f]{6})$/i.exec(QS.get("bg") || "");
if (m) {
BG = [0, 2, 4].map(i => parseInt(m[1].substr(i, 2), 16));
document.documentElement.style.setProperty("--bg", "#" + m[1]);
}
}
// ---------- Zufall (seedbar) ----------
function mulberry32(a) {
return function () {
a |= 0; a = a + 0x6D2B79F5 | 0;
let t = Math.imul(a ^ a >>> 15, 1 | a);
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
return ((t ^ t >>> 14) >>> 0) / 4294967296;
};
}
// v = Stimmen/Details, s = Szene (Tonart, Klangfarbe), n = Sonstiges
const rng = { v: Math.random, s: Math.random, n: Math.random };
const rnd = (a, b, r = rng.v) => a + r() * (b - a);
const pick = (arr, r = rng.v) => arr[Math.floor(r() * arr.length)];
const mtof = m => 440 * Math.pow(2, (m - 69) / 12);
const clamp = (x, a, b) => Math.min(b, Math.max(a, x));
function toSeed(str) {
if (/^\d+$/.test(str)) return parseInt(str, 10) % 2147483647 || 1;
let h = 2166136261;
for (const ch of str) h = Math.imul(h ^ ch.charCodeAt(0), 16777619);
return (h >>> 0) % 2147483647 || 1;
}
function seedAll(seed) {
rng.v = mulberry32(seed); rng.s = mulberry32(seed ^ 0x9e3779b9); rng.n = mulberry32(seed ^ 0x85ebca6b);
}
// ---------- Musikalische Daten ----------
const NOTE = ["C","C♯","D","D♯","E","F","F♯","G","G♯","A","A♯","H"];
const SCALES = {
"Dorisch": [0,2,3,5,7,9,10],
"Lydisch": [0,2,4,6,7,9,11],
"Äolisch": [0,2,3,5,7,8,10],
"Mixolydisch": [0,2,4,5,7,9,10],
"Phrygisch": [0,1,3,5,7,8,10],
"Moll-Pentatonik": [0,3,5,7,10],
"Dur-Pentatonik": [0,2,4,7,9],
"Ganzton": [0,2,4,6,8,10],
};
// Reine Stimmung: Frequenzverhältnisse pro Halbton über dem Grundton
const JI = [1, 16/15, 9/8, 6/5, 5/4, 4/3, 45/32, 3/2, 8/5, 5/3, 9/5, 15/8];
const PRESETS = {
"Frei": { scales:Object.keys(SCALES), bright:[.1,.95], fm:[0,.8], saw:[0,.9], wt:[0,.7], noise:[.05,.5], grain:[0,.6], choir:.15, bells:1, swell:.3, dens:1 },
"Dunkel": { scales:["Phrygisch","Äolisch","Moll-Pentatonik"], bright:[.05,.35], fm:[0,.25], saw:[.3,.9], wt:[0,.3], noise:[.3,.8], grain:[.2,.6], choir:.1, bells:.4, swell:.5, dens:.8 },
"Schwebend": { scales:["Lydisch","Dur-Pentatonik","Dorisch"], bright:[.3,.7], fm:[.05,.4], saw:[0,.4], wt:[.2,.7], noise:[0,.25], grain:[0,.3], choir:.4, bells:1, swell:.4, dens:1 },
"Glasig": { scales:["Ganzton","Dur-Pentatonik","Lydisch"], bright:[.6,.95], fm:[.4,.9], saw:[0,.2], wt:[.4,.9], noise:[0,.1], grain:[0,.2], choir:0, bells:1.8, swell:.2, dens:1.1 },
"Wald": { scales:["Dorisch","Mixolydisch","Moll-Pentatonik"], bright:[.2,.55], fm:[0,.2], saw:[.2,.6], wt:[0,.4], noise:[.5,1], grain:[.3,.8], choir:.5, bells:.6, swell:.3, dens:.9 },
};
const VOWELS = { // Formanten F1,F2,F3 und relative Pegel
a:[800,1150,2900,1,.5,.25], o:[450,800,2830,1,.45,.1], u:[325,700,2700,1,.3,.05],
e:[400,1600,2700,1,.35,.15], i:[270,2140,2950,1,.25,.12],
};
const LOOP_PERIODS = [17.3, 23.1, 29.7, 37.9, 43.3, 53.1]; // Eno-Stil: unterschiedlich lange Zyklen
// ---------- Zustand ----------
const S = {
ctx: null, running: false, g: null,
seedText: "", seed: 1, presetName: "Frei", P: PRESETS["Frei"], tuning: "just",
root: 38, scaleName: "Dorisch", scale: SCALES["Dorisch"], degree: 0,
// Klangfarbe: aktuelle Werte (cur) wandern langsam zu Zielwerten (tgt)
cur: { bright:.35, fm:.15, saw:.3, wt:.2, noise:.2, grain:.2, detune:6, ratio:2, motion:.3 },
tgt: { bright:.35, fm:.15, saw:.3, wt:.2, noise:.2, grain:.2, detune:6, ratio:2, motion:.3 },
density:.5, drift:.5, verb:.6, arcDepth:.6, arcLen:700, t0:0,
timers: [], waves: [], loop: { deg:[], oct:[] }, vis: [],
ring: null, wp: 0, written: 0, grainBuf: null,
};
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
let rafId = 0; // Zeichenschleife (siehe wake/draw)
2026-10-04 00:01:03 +02:00
S.hostVol = null; // Lautstärke der einbettenden Seite (cmd "volume"), sonst gilt der eigene Regler
const outVol = () => S.hostVol != null ? S.hostVol : +$("vol").value;
2026-10-03 23:08:57 +02:00
const speed = () => 0.35 + S.drift * 2.2;
// Spannungsbogen: 0 = ruhig, 1 = Höhepunkt, über mehrere Minuten
function arcPhase() { return S.running ? (((S.ctx.currentTime - S.t0) / S.arcLen) % 1) : 0; }
function eff() {
const I = 0.5 - 0.5 * Math.cos(2 * Math.PI * arcPhase());
return (1 - S.arcDepth) * .6 + S.arcDepth * I;
}
const effDens = () => clamp(S.density * (.5 + .9 * eff()) * S.P.dens, 0, 1);
// ---------- Audio-Graph ----------
function makeImpulse(ctx, secs, decay) {
const len = Math.floor(ctx.sampleRate * secs), buf = ctx.createBuffer(2, len, ctx.sampleRate);
for (let c = 0; c < 2 ; c + + ) {
const d = buf.getChannelData(c); let lp = 0;
for (let i = 0; i < len ; i + + ) {
const t = i / len;
lp += (Math.random() * 2 - 1 - lp) * (0.35 - 0.3 * t);
d[i] = lp * Math.pow(1 - t, decay) * 2.2;
}
}
return buf;
}
function pinkBuffer(ctx, secs) {
const len = ctx.sampleRate * secs, buf = ctx.createBuffer(1, len, ctx.sampleRate), d = buf.getChannelData(0);
let b0=0,b1=0,b2=0,b3=0,b4=0,b5=0,b6=0, peak = 0;
for (let i = 0; i < len ; i + + ) {
const w = Math.random() * 2 - 1;
b0=.99886*b0+w*.0555179; b1=.99332*b1+w*.0750759; b2=.969*b2+w*.153852;
b3=.8665*b3+w*.3104856; b4=.55*b4+w*.5329522; b5=-.7616*b5-w*.016898;
d[i] = b0+b1+b2+b3+b4+b5+b6+w*.5362; b6 = w*.115926; peak = Math.max(peak, Math.abs(d[i]));
}
for (let i = 0; i < len ; i + + ) d [ i ] = d [ i ] / peak * . 5 ;
return buf;
}
function satCurve(k) {
const n = 2048, c = new Float32Array(n);
for (let i = 0; i < n ; i + + ) { const x = i / ( n - 1 ) * 2 - 1 ; c [ i ] = Math . tanh ( k * x ) / Math . tanh ( k ) ; }
return c;
}
function buildGraph() {
2026-10-04 00:01:03 +02:00
// "playback": the normal media path (on Android the low-latency one can stay silent, e.g. on Samsung)
const ctx = S.ctx = new (window.AudioContext || window.webkitAudioContext)({ latencyHint: "playback" });
2026-10-03 23:08:57 +02:00
const g = S.g = {};
g.voiceBus = ctx.createGain();
g.filter = ctx.createBiquadFilter();
g.filter.type = "lowpass"; g.filter.Q.value = .6; g.filter.frequency.value = 1800;
g.voiceBus.connect(g.filter);
g.lfo = ctx.createOscillator(); g.lfo.frequency.value = .04;
g.lfoAmt = ctx.createGain(); g.lfoAmt.gain.value = 500;
g.lfo.connect(g.lfoAmt).connect(g.filter.frequency); g.lfo.start();
// gemeinsamer Summenbus -> Tape-Sättigung -> Kompressor
g.mix = ctx.createGain();
g.drive = ctx.createGain(); g.drive.gain.value = 1.5;
2026-10-04 00:09:28 +02:00
g.shaper = ctx.createWaveShaper(); g.shaper.curve = satCurve(1.6); g.shaper.oversample = LITE ? "none" : "2x";
2026-10-03 23:08:57 +02:00
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;
g.master = ctx.createGain(); g.master.gain.value = 0;
g.mix.connect(g.drive).connect(g.shaper).connect(g.makeup).connect(g.comp).connect(g.master);
g.dry = ctx.createGain(); g.dry.gain.value = .8;
g.filter.connect(g.dry).connect(g.mix);
// Hall
2026-10-04 00:09:28 +02:00
g.conv = ctx.createConvolver(); g.conv.buffer = makeImpulse(ctx, LITE ? 3.5 : 7, 2.6);
2026-10-03 23:08:57 +02:00
g.send = ctx.createGain(); g.send.gain.value = S.verb;
g.filter.connect(g.send).connect(g.conv);
g.conv.connect(g.mix);
// Tape-Delay mit Hochpass im Feedback und Wow/Flutter
g.delay = ctx.createDelay(4); g.delay.delayTime.value = .62;
g.dlp = ctx.createBiquadFilter(); g.dlp.type = "lowpass"; g.dlp.frequency.value = 2400;
g.dhp = ctx.createBiquadFilter(); g.dhp.type = "highpass"; g.dhp.frequency.value = 180;
g.fb = ctx.createGain(); g.fb.gain.value = .42;
g.delaySend = ctx.createGain();
g.delaySend.connect(g.delay); g.delay.connect(g.dlp);
g.dlp.connect(g.dhp).connect(g.fb).connect(g.delay);
g.dlp.connect(g.conv); g.dlp.connect(g.mix);
g.wow = ctx.createOscillator(); g.wow.frequency.value = .37;
g.wowC = ctx.createGain(); g.wowC.gain.value = 7; // Cent
g.flut = ctx.createOscillator(); g.flut.frequency.value = 5.3;
g.flutC = ctx.createGain(); g.flutC.gain.value = 1.6;
g.pitchMod = ctx.createGain();
g.wow.connect(g.wowC).connect(g.pitchMod); g.flut.connect(g.flutC).connect(g.pitchMod);
g.wow.start(); g.flut.start();
g.dWow = ctx.createGain(); g.dWow.gain.value = .004; // Delaytime-Wackeln
g.wow.connect(g.dWow).connect(g.delay.delayTime);
// Rauschbett: Wind (Bandpass, wandernd) + Meer (Tiefpass, Swell)
const nb = pinkBuffer(ctx, 8);
g.noiseBus = ctx.createGain();
const nA = ctx.createBufferSource(); nA.buffer = nb; nA.loop = true;
g.bp = ctx.createBiquadFilter(); g.bp.type = "bandpass"; g.bp.Q.value = 1.4; g.bp.frequency.value = 700;
const wMod = ctx.createGain(); wMod.gain.value = .65;
g.windGain = ctx.createGain(); g.windGain.gain.value = 0;
const wl = ctx.createOscillator(); wl.frequency.value = .07; const wlg = ctx.createGain(); wlg.gain.value = .35;
wl.connect(wlg).connect(wMod.gain); wl.start();
const wf = ctx.createOscillator(); wf.frequency.value = .05; const wfg = ctx.createGain(); wfg.gain.value = 350;
wf.connect(wfg).connect(g.bp.frequency); wf.start();
nA.connect(g.bp).connect(wMod).connect(g.windGain).connect(g.noiseBus);
const nB = ctx.createBufferSource(); nB.buffer = nb; nB.loop = true;
const sl = ctx.createBiquadFilter(); sl.type = "lowpass"; sl.frequency.value = 420;
const sMod = ctx.createGain(); sMod.gain.value = .55;
g.seaGain = ctx.createGain(); g.seaGain.gain.value = 0;
const sw = ctx.createOscillator(); sw.frequency.value = .11; const swg = ctx.createGain(); swg.gain.value = .45;
sw.connect(swg).connect(sMod.gain); sw.start();
nB.connect(sl).connect(sMod).connect(g.seaGain).connect(g.noiseBus);
nA.start(); nB.start(0, 3.1);
g.noiseBus.connect(g.mix);
2026-10-04 00:18:06 +02:00
const nSend = g.nSend = ctx.createGain(); nSend.gain.value = .3; g.noiseBus.connect(nSend).connect(g.conv);
S.musicLinked = true; S.revLinked = true; // siehe applyRev (Telefone)
2026-10-03 23:08:57 +02:00
2026-10-04 12:38:06 +02:00
// Granular: Ringpuffer der Stimmen, daraus werden Körner geschnitten. Er läuft als AudioWorklet
// im Audio-Thread – ein ScriptProcessor im Hauptthread hielt bei jedem Stocken der Seite den
// ganzen Ton auf (gehäufte Aussetzer). Telefone: keine Körner.
if (!LITE & & ctx.audioWorklet) {
const url = URL.createObjectURL(new Blob([RING_WORKLET], { type: "application/javascript" }));
ctx.audioWorklet.addModule(url).then(() => {
const node = new AudioWorkletNode(ctx, "drift-ring", { numberOfInputs: 1, numberOfOutputs: 0, processorOptions: { n: ctx.sampleRate * 12 } });
node.port.onmessage = e => {
const d = e.data;
if (!d || d.length < ctx.sampleRate * 6 ) return ;
const b = ctx.createBuffer(1, d.length, ctx.sampleRate); b.copyToChannel(d, 0); S.grainBuf = b;
};
g.voiceBus.connect(node); S.ringNode = node;
}).catch(() => {}).finally(() => URL.revokeObjectURL(url));
2026-10-04 00:09:28 +02:00
}
2026-10-03 23:08:57 +02:00
g.grainBus = ctx.createGain(); g.grainBus.connect(g.filter);
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
g.modeBus = ctx.createGain(); g.modeBus.connect(g.mix); // Klangwelten (Rauschen, Natur, …)
g.modeRev = ctx.createGain(); g.modeRev.gain.value = 0; g.modeBus.connect(g.modeRev).connect(g.conv);
2026-10-03 23:08:57 +02:00
g.analyser = ctx.createAnalyser(); g.analyser.fftSize = 2048; g.analyser.smoothingTimeConstant = .88;
g.master.connect(g.analyser); g.master.connect(ctx.destination);
}
function makeWaves() { // zufällige Wavetable-Zyklen aus Obertonreihen
const out = [];
for (let w = 0; w < 8 ; w + + ) {
const re = new Float32Array(17), im = new Float32Array(17), p = rnd(.8, 2.2, rng.n);
for (let k = 1; k < 17 ; k + + ) {
if (rng.n() < .75 ) { const a = 1 / Math . pow ( k , p ) , ph = rnd(0, 6 . 28 , rng . n ) ; re [ k ] = a * Math . cos ( ph ) ; im [ k ] = a * Math . sin ( ph ) ; }
}
im[1] = Math.max(im[1], .6);
out.push(S.ctx.createPeriodicWave(re, im));
}
return out;
}
// ---------- Frequenzen ----------
function freqFromDegree(deg) {
const n = S.scale.length, semi = S.scale[((deg % n) + n) % n], oct = Math.floor(deg / n);
const ratio = S.tuning === "just" ? JI[semi] : Math.pow(2, semi / 12);
return mtof(S.root) * ratio * Math.pow(2, oct);
}
const emit = (f, when, dur, kind) => { if (S.vis.length < 200 ) S . vis . push ( { f , t: when , dur , kind } ) ; } ;
// ---------- Stimmen ----------
function envelope(g, when, att, dur, rel, peak) {
g.gain.setValueAtTime(0, when);
g.gain.linearRampToValueAtTime(peak, when + att);
g.gain.setValueAtTime(peak, when + Math.max(att, dur - rel));
g.gain.linearRampToValueAtTime(0, when + dur + rel * .2);
return when + dur + rel * .2 + .1;
}
// Pad: detunte Säge/Dreieck + Sinus-Sub + Wavetable-Morph + FM, eigener Filter, Tape-Wow
2026-10-04 10:42:45 +02:00
// Telefone: zählt die klingenden Stimmen; false, wenn schon genug klingen.
function voiceSlot(when, end, always) {
if (!LITE) return true;
if (!always & & S.voices >= MAX_VOICES) return false;
S.voices = (S.voices || 0) + 1;
setTimeout(() => { S.voices--; }, Math.max(0, end - S.ctx.currentTime) * 1000 + 200);
return true;
}
2026-10-03 23:08:57 +02:00
function padVoice(f, when, dur, vel = 1) {
const ctx = S.ctx, c = S.cur, G = S.g;
2026-10-04 10:42:45 +02:00
if (!voiceSlot(when, when + dur + 10, vel >= 1.4)) return;
2026-10-03 23:08:57 +02:00
const out = ctx.createGain(), flt = ctx.createBiquadFilter(), pan = ctx.createStereoPanner();
flt.type = "lowpass"; flt.Q.value = .5;
flt.frequency.value = clamp(f * (1.2 + c.bright * 7), 200, 9000);
pan.pan.value = rnd(-.7, .7);
const att = rnd(2.5, 6) / Math.sqrt(speed()), rel = rnd(5, 9);
const end = envelope(out, when, att, dur, rel, .07 * vel);
const oscs = [];
const add = (o, gain) => {
const g = ctx.createGain(); g.gain.value = gain;
o.connect(g).connect(flt); G.pitchMod.connect(o.detune);
o.start(when); o.stop(end); oscs.push(o); return g;
};
const mk = (type, mul, det, gain) => {
const o = ctx.createOscillator(); o.type = type; o.frequency.value = f * mul; o.detune.value = det;
return add(o, gain);
};
2026-10-04 10:42:45 +02:00
mk("sawtooth", 1, c.detune * rnd(.6, 1.2), (LITE ? .5 : .35) * c.saw);
if (!LITE) mk("sawtooth", 1, -c.detune * rnd(.6, 1.2), .35 * c.saw); // Telefone: eine Säge statt zwei
2026-10-03 23:08:57 +02:00
mk("triangle", 1, c.detune * .3, .5 * (1 - c.saw * .5) * (1 - c.wt * .6));
mk("sine", .5, 0, .35);
if (c.wt > .04) { // zwei Wavetable-Zyklen, über die Notendauer übergeblendet
const mkW = w => { const o = ctx.createOscillator(); o.setPeriodicWave(w); o.frequency.value = f; return o; };
2026-10-04 10:42:45 +02:00
const ga = add(mkW(pick(S.waves)), .45 * c.wt);
if (!LITE) { // Telefone: ein Zyklus ohne Überblendung
const gb = add(mkW(pick(S.waves)), 0);
ga.gain.setValueAtTime(.45 * c.wt, when); ga.gain.linearRampToValueAtTime(0, when + dur);
gb.gain.setValueAtTime(0, when); gb.gain.linearRampToValueAtTime(.45 * c.wt, when + dur);
}
2026-10-03 23:08:57 +02:00
}
2026-10-04 10:42:45 +02:00
if (c.fm > (LITE ? .25 : .03)) { // Telefone: FM nur, wo sie deutlich hörbar ist
2026-10-03 23:08:57 +02:00
const m = ctx.createOscillator(); m.type = "sine"; m.frequency.value = f * c.ratio;
const mg = ctx.createGain(); mg.gain.value = f * c.fm * 1.6;
const car = ctx.createOscillator(); car.type = "sine"; car.frequency.value = f;
const cg = ctx.createGain(); cg.gain.value = .3;
m.connect(mg).connect(car.frequency); car.connect(cg).connect(flt);
m.start(when); car.start(when); m.stop(end); car.stop(end); oscs.push(m, car);
}
const vib = ctx.createOscillator(); vib.frequency.value = rnd(.15, .6);
const vg = ctx.createGain(); vg.gain.value = flt.frequency.value * c.motion * .5;
vib.connect(vg).connect(flt.frequency); vib.start(when); vib.stop(end); oscs.push(vib);
flt.connect(out).connect(pan).connect(G.voiceBus);
oscs[0].onended = () => { try { pan.disconnect(); } catch (e) {} };
emit(f, when + att * .5, dur, "pad");
}
// Chor: Sägezahn durch drei Formantfilter, Vokal gleitet während der Note
function choirVoice(f, when, dur) {
const ctx = S.ctx, G = S.g;
2026-10-04 10:42:45 +02:00
if (!voiceSlot(when, when + dur + 8)) return;
2026-10-03 23:08:57 +02:00
const out = ctx.createGain(), pan = ctx.createStereoPanner(), src = ctx.createGain();
pan.pan.value = rnd(-.6, .6);
const att = rnd(3, 6), rel = rnd(5, 8), end = envelope(out, when, att, dur, rel, .16);
const A = VOWELS[pick(Object.keys(VOWELS))], B = VOWELS[pick(Object.keys(VOWELS))];
const oscs = [];
[-9, 9].forEach(det => {
const o = ctx.createOscillator(); o.type = "sawtooth"; o.frequency.value = f; o.detune.value = det;
G.pitchMod.connect(o.detune); o.connect(src); o.start(when); o.stop(end); oscs.push(o);
});
const vib = ctx.createOscillator(); vib.frequency.value = rnd(4.6, 5.8);
const vg = ctx.createGain(); vg.gain.setValueAtTime(0, when); vg.gain.linearRampToValueAtTime(11, when + att + 2);
vib.connect(vg); oscs.forEach(o => vg.connect(o.detune)); vib.start(when); vib.stop(end);
for (let i = 0; i < 3 ; i + + ) {
const bp = ctx.createBiquadFilter(); bp.type = "bandpass"; bp.Q.value = 9;
bp.frequency.setValueAtTime(A[i], when); bp.frequency.linearRampToValueAtTime(B[i], when + dur);
const fg = ctx.createGain(); fg.gain.value = (A[3 + i] + B[3 + i]) * 1.4;
src.connect(bp).connect(fg).connect(out);
}
out.connect(pan).connect(G.voiceBus);
oscs[0].onended = () => { try { pan.disconnect(); } catch (e) {} };
emit(f, when + att * .5, dur, "choir");
}
// Glocke: FM-Pluck in Delay + Hall
function bell(f, when) {
const ctx = S.ctx, c = S.cur;
const car = ctx.createOscillator(), mod = ctx.createOscillator();
const mg = ctx.createGain(), out = ctx.createGain(), pan = ctx.createStereoPanner();
car.frequency.value = f; mod.frequency.value = f * pick([2, 3, 3.5, 4, 5.04]);
const idx = f * (.4 + c.fm * 2.5 + c.bright);
mg.gain.setValueAtTime(idx, when); mg.gain.exponentialRampToValueAtTime(1, when + 2.5);
const dec = rnd(3, 6);
out.gain.setValueAtTime(0, when);
out.gain.linearRampToValueAtTime(rnd(.03, .06), when + .012);
out.gain.exponentialRampToValueAtTime(.0001, when + dec);
pan.pan.value = rnd(-.8, .8);
mod.connect(mg).connect(car.frequency);
car.connect(out).connect(pan).connect(S.g.voiceBus);
const ds = ctx.createGain(); ds.gain.value = .7; pan.connect(ds).connect(S.g.delaySend);
car.start(when); mod.start(when); car.stop(when + dec + .1); mod.stop(when + dec + .1);
emit(f, when, dec, "bell");
}
// Rückwärts-Swell: Ton samt Hallfahne offline rendern und umdrehen
async function renderSwell(f) {
const sr = S.ctx.sampleRate, oc = new OfflineAudioContext(2, Math.ceil(sr * 4.5), sr);
const lp = oc.createBiquadFilter(); lp.type = "lowpass"; lp.frequency.value = clamp(f * 5, 400, 5000);
const env = oc.createGain();
env.gain.setValueAtTime(0, 0); env.gain.linearRampToValueAtTime(.3, .01); env.gain.exponentialRampToValueAtTime(.001, 1.6);
[["sawtooth", 1, 0], ["sawtooth", 1, 7], ["sine", .5, 0]].forEach(([t, m, d]) => {
const o = oc.createOscillator(); o.type = t; o.frequency.value = f * m; o.detune.value = d;
o.connect(lp); o.start(0);
});
lp.connect(env);
const conv = oc.createConvolver(); conv.buffer = makeImpulse(oc, 3.4, 2.2);
env.connect(conv).connect(oc.destination);
const dry = oc.createGain(); dry.gain.value = .4; env.connect(dry).connect(oc.destination);
const buf = await oc.startRendering();
for (let c = 0; c < buf.numberOfChannels ; c + + ) buf . getChannelData ( c ) . reverse ( ) ;
return buf;
}
function playSwell(buf, t) {
const ctx = S.ctx, src = ctx.createBufferSource(), g = ctx.createGain();
src.buffer = buf; g.gain.setValueAtTime(0, t); g.gain.linearRampToValueAtTime(.9, t + .6);
src.connect(g).connect(S.g.voiceBus); src.start(t);
}
// Granulare Wolke aus dem eigenen Output
2026-10-04 12:38:06 +02:00
// Der Ringpuffer im Audio-Thread: auf Anfrage eine Kopie der letzten Sekunden (siehe snapGrains).
const RING_WORKLET = `
class DriftRing extends AudioWorkletProcessor {
constructor(o) {
super();
this.n = o.processorOptions.n; this.buf = new Float32Array(this.n); this.wp = 0; this.written = 0;
this.port.onmessage = () => {
const len = Math.min(this.written, this.n), out = new Float32Array(len), start = (this.wp - len + this.n) % this.n;
if (start + len < = this.n) out.set(this.buf.subarray(start, start + len));
else { out.set(this.buf.subarray(start)); out.set(this.buf.subarray(0, len - (this.n - start)), this.n - start); }
this.port.postMessage(out, [out.buffer]);
};
}
process(inputs) {
const inp = inputs[0] & & inputs[0][0];
if (inp) {
const n = this.n, k = inp.length, room = n - this.wp;
if (k < = room) this.buf.set(inp, this.wp); else { this.buf.set(inp.subarray(0, room), this.wp); this.buf.set(inp.subarray(room), 0); }
this.wp = (this.wp + k) % n; this.written += k;
}
return true;
}
}
registerProcessor("drift-ring", DriftRing);
`;
2026-10-03 23:08:57 +02:00
function snapGrains() {
2026-10-04 12:38:06 +02:00
if (S.ringNode) S.ringNode.port.postMessage("snap"); // die Antwort setzt S.grainBuf
2026-10-03 23:08:57 +02:00
}
function grainCloud() {
const amt = S.cur.grain * eff();
if (!S.running || !S.grainBuf || amt < .05 ) return ;
const ctx = S.ctx, buf = S.grainBuf, n = Math.round(6 + rnd(0, 8) * (.5 + amt)), t0 = ctx.currentTime + .1;
const rate = pick([.5, 1, 1, 1.5, 2]);
const pos = rnd(0, buf.duration - 3);
for (let i = 0; i < n ; i + + ) {
const when = t0 + rnd(0, 3.5), dur = rnd(.35, 1.5), src = ctx.createBufferSource(), g = ctx.createGain(), pn = ctx.createStereoPanner();
src.buffer = buf; src.playbackRate.value = rate * rnd(.995, 1.005);
g.gain.setValueAtTime(0, when); g.gain.linearRampToValueAtTime(.55 * amt, when + dur * .5); g.gain.linearRampToValueAtTime(0, when + dur);
pn.pan.value = rnd(-.9, .9);
src.connect(g).connect(pn).connect(S.g.grainBus);
src.start(when, clamp(pos + rnd(0, 2.5), 0, buf.duration - dur * rate - .05), dur * rate);
}
}
function drone() {
const f = mtof(S.root) * .5 * (rng.v() < .3 ? 1 . 5 : 1 ) ;
padVoice(f, S.ctx.currentTime + .1, rnd(25, 45), 1.4);
}
// ---------- Harmonie / Szene ----------
function spawnChord(t0) {
const n = S.scale.length, d = effDens();
const stack = [0, 2, 4];
if (rng.s() < .5 ) stack . push ( pick ( [ 1 , 6 , 8 ] , rng . s ) ) ;
const count = Math.round(2 + d * 3);
const chosen = stack.sort(() => rng.s() - .5).slice(0, count);
const oct = pick([1, 1, 2], rng.s);
chosen.forEach((st, i) => {
const deg = S.degree + st + n * (oct + (rng.s() < .35 ? 1 : 0 ) ) ;
const f = freqFromDegree(deg), when = t0 + i * rnd(.5, 3.5), dur = rnd(11, 20) / Math.sqrt(speed());
if (rng.v() < S.P.choir & & f < 700 ) choirVoice ( f , when , dur ) ; else padVoice ( f , when , dur ) ;
});
}
function playChord() {
if (!S.running) return;
2026-10-04 10:42:45 +02:00
// Rückwärts-Swell: offline gerendert, auf Telefonen zu teuer (eine Rechenspitze, der Ton stockt)
if (!LITE & & rng.v() < S.P.swell ) {
2026-10-03 23:08:57 +02:00
renderSwell(freqFromDegree(S.degree + S.scale.length)).then(buf => {
if (!S.running) return;
const t = S.ctx.currentTime + .1;
playSwell(buf, t); spawnChord(t + buf.duration - .3);
});
} else spawnChord(S.ctx.currentTime + .2);
}
function playBells() {
if (!S.running) return;
if (rng.v() < (.2 + effDens() * .55) * S.P.bells) {
const n = S.scale.length, now = S.ctx.currentTime;
const deg = S.degree + pick([0, 2, 4, 1, 3, 6]) + n * pick([3, 3, 4]);
bell(freqFromDegree(deg), now + .05);
if (rng.v() < .25 ) bell ( freqFromDegree ( deg + pick ( [ 2 , 4 , -2 ] ) ) , now + rnd ( . 3 , 1 . 4 ) ) ;
}
}
function setupLoops() {
S.loop.deg = LOOP_PERIODS.map(() => pick([0, 2, 4, 1, 3, 5, 6], rng.s));
S.loop.oct = LOOP_PERIODS.map(() => pick([1, 1, 2, 2, 3], rng.s));
}
function startLoops() { // jede Schleife hat ihren festen Zyklus, nichts wiederholt sich hörbar
LOOP_PERIODS.forEach((p, i) => {
const slot = S.timers.length; S.timers.push(0);
const fire = () => {
if (!S.running) return;
if (i < 1 + Math . round ( effDens ( ) * 4 ) ) {
const f = freqFromDegree(S.loop.deg[i] + S.scale.length * S.loop.oct[i]), when = S.ctx.currentTime + .1;
if (i % 2) bell(f, when); else padVoice(f, when, rnd(8, 14), .6);
}
S.timers[slot] = setTimeout(fire, p * 1000);
};
S.timers[slot] = setTimeout(fire, rnd(1, p, rng.n) * 1000);
});
}
function changeDegree() {
const n = S.scale.length;
S.degree = pick([0, 0, n - 1, 1, 3, 4, 5, 2, 4, n - 2].map(d => d % n), rng.s);
S.g.delay.delayTime.setTargetAtTime(pick([.45, .62, .75, .9], rng.s), S.ctx.currentTime, 3);
}
function changeKey(soft = true) {
let r = S.root + pick([7, 5, 2, -2, 3, -3, 0, 7, 5], rng.s);
while (r > 47) r -= 12; while (r < 36 ) r + = 12 ;
S.root = r;
S.scaleName = pick(S.P.scales, rng.s); S.scale = SCALES[S.scaleName];
S.degree = 0;
if (!soft & & S.running) drone();
}
function wanderTimbre() {
const P = S.P, r = rng.s, between = ([a, b]) => rnd(a, b, r);
S.tgt = {
bright: between(P.bright), fm: between(P.fm), saw: between(P.saw), wt: between(P.wt),
noise: between(P.noise), grain: between(P.grain),
detune: rnd(2, 18, r), ratio: pick([1, 2, 2, 3, 1.5, .5, 4, 2.01, 3.01], r), motion: rnd(.05, .6, r),
};
}
// ---------- Scheduler ----------
function later(fn, minS, maxS, salt, first = .3) {
const slot = S.timers.length; S.timers.push(0);
const r = mulberry32(S.seed * 31 + salt * 7919);
const loop = () => {
if (!S.running) return;
fn();
S.timers[slot] = setTimeout(loop, rnd(minS, maxS, r) * 1000 / speed());
};
S.timers[slot] = setTimeout(loop, rnd(minS, maxS, r) * 1000 / speed() * first);
}
function tick() {
if (!S.running) return;
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
if (!MODES[S.mode].music) { tickMode(); return; }
2026-10-03 23:08:57 +02:00
const e = eff(), k = .024 * speed(), t = S.ctx.currentTime, G = S.g;
for (const key in S.cur) S.cur[key] += (S.tgt[key] - S.cur[key]) * k;
G.filter.frequency.setTargetAtTime(500 + S.cur.bright * 4500 * (.6 + .6 * e), t, 2);
G.lfo.frequency.setTargetAtTime(.02 + S.cur.motion * .12, t, 4);
G.lfoAmt.gain.setTargetAtTime(150 + S.cur.bright * 1200, t, 4);
const nl = S.cur.noise * (1.3 - .6 * e);
G.windGain.gain.setTargetAtTime(.3 * nl, t, 3);
G.seaGain.gain.setTargetAtTime(.35 * nl, t, 3);
G.bp.frequency.setTargetAtTime(400 + S.cur.bright * 1400, t, 5);
renderMeters(); updateInfo();
}
function resetScene() {
S.t0 = S.ctx.currentTime;
seedAll(S.seed);
S.arcLen = rnd(540, 840, rng.n);
S.waves = makeWaves();
wanderTimbre(); S.cur = { ...S.tgt };
changeKey(true); setupLoops();
}
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
// ---------- Klangwelten (Modi) ----------
// Musik ist nur ein Modus von vielen. Alle anderen sind ebenfalls komplett synthetisiert.
S.mode = "ambient"; S.modeObj = null; S.mp = { a: .5, b: .5 }; S.flash = 0; S.tickId = 0;
const T = () => S.ctx.currentTime;
const sm = (p, v, tc = .6) => p.setTargetAtTime(v, T(), tc);
const R = (a, b) => a + Math.random() * (b - a);
function bq(type, f, Q = .7, gain = 0) {
const n = S.ctx.createBiquadFilter(); n.type = type; n.frequency.value = f; n.Q.value = Q; n.gain.value = gain; return n;
}
// Rauschfarben: 10 s Stereo-Loop pro Typ, Naht per Equal-Power-Crossfade, auf gleiche Lautheit normalisiert
const NB = {};
function noiseBuf(type) {
if (NB[type]) return NB[type];
const ctx = S.ctx, sr = ctx.sampleRate, len = sr * 10, fade = Math.floor(sr * .5), tot = len + fade;
const buf = ctx.createBuffer(2, len, sr);
for (let c = 0; c < 2 ; c + + ) {
const raw = new Float32Array(tot);
let b0=0,b1=0,b2=0,b3=0,b4=0,b5=0,b6=0, brown=0, prevP=0, prevW=0;
for (let i = 0; i < tot ; i + + ) {
const w = Math.random() * 2 - 1;
if (type === "white") raw[i] = w;
else if (type === "violet") { raw[i] = w - prevW; prevW = w; }
else if (type === "brown") { brown = (brown + .02 * w) / 1.02; raw[i] = brown; }
else { // pink, blue (= Ableitung von pink)
b0=.99886*b0+w*.0555179; b1=.99332*b1+w*.0750759; b2=.969*b2+w*.153852;
b3=.8665*b3+w*.3104856; b4=.55*b4+w*.5329522; b5=-.7616*b5-w*.016898;
const p = b0+b1+b2+b3+b4+b5+b6+w*.5362; b6 = w*.115926;
if (type === "pink") raw[i] = p; else { raw[i] = p - prevP; prevP = p; }
}
}
let sum = 0; for (let i = 0; i < tot ; i + + ) sum + = raw [ i ] * raw [ i ] ;
const k = .2 / Math.sqrt(sum / tot + 1e-12), d = buf.getChannelData(c);
for (let i = 0; i < len ; i + + ) d [ i ] = clamp ( raw [ i ] * k , -1 , 1 ) ;
for (let i = 0; i < fade ; i + + ) {
const th = i / fade * Math.PI / 2;
d[i] = clamp((raw[i] * Math.sin(th) + raw[len + i] * Math.cos(th)) * k, -1, 1);
}
}
return NB[type] = buf;
}
// Einzelereignisse (Tropfen, Knistern, Blubbern), kreisförmig in 10 s geschrieben -> nahtloser Loop
const DB = {};
function dropBuf(kind, rate) {
const key = kind + rate; if (DB[key]) return DB[key];
const ctx = S.ctx, sr = ctx.sampleRate, len = sr * 10, buf = ctx.createBuffer(2, len, sr);
const target = { rain: .16, pop: .09, water: .14 }[kind];
for (let c = 0; c < 2 ; c + + ) {
const d = buf.getChannelData(c), N = Math.round(rate * 10);
if (kind === "water") fillWater(d, len, sr, rate);
else for (let e = 0; e < N ; e + + ) {
const pos = Math.floor(Math.random() * len);
if (kind === "rain") {
const n = 40 + Math.floor(Math.random() * 220), amp = Math.pow(Math.random(), 2.2) * .9, k = .12 + Math.random() * .75;
let lp = 0;
for (let j = 0; j < n ; j + + ) { lp + = ( Math . random ( ) * 2 - 1 - lp ) * k ; d [ ( pos + j ) % len ] + = lp * Math . exp ( -j / ( n * . 25 ) ) * amp ; }
} else if (kind === "pop") {
const burst = Math.random() < .25 ? 2 + Math . floor ( Math . random ( ) * 5 ) : 1 ;
for (let b = 0; b < burst ; b + + ) {
const p = (pos + b * Math.floor(sr * (.004 + Math.random() * .02))) % len;
const n = 10 + Math.floor(Math.random() * 70), amp = Math.pow(Math.random(), 3) * 1.2, k = .35 + Math.random() * .6;
let lp = 0;
for (let j = 0; j < n ; j + + ) { lp + = ( Math . random ( ) * 2 - 1 - lp ) * k ; d [ ( p + j ) % len ] + = lp * Math . exp ( -j / ( n * . 3 ) ) * amp ; }
}
}
}
let sum = 0; for (let i = 0; i < len ; i + + ) sum + = d [ i ] * d [ i ] ;
const k = target / Math.sqrt(sum / len + 1e-12);
for (let i = 0; i < len ; i + + ) d [ i ] = clamp ( d [ i ] * k , -1 , 1 ) ;
}
return DB[key] = buf;
}
2026-10-03 23:41:48 +02:00
// Wasser: Luftbläschen als gedämpfte, ansteigende Sinus-Schwingungen (Minnaert-Modell: Frequenz ≈ 3 / Radius,
// Dämpfung wächst mit der Frequenz). Ein Bach "gluckst" in Gruppen: ähnlich große Bläschen platzen kurz hintereinander,
// jede Gruppe hat ihre eigene Lautstärke, und die Gruppenrate schwankt periodisch über die 10 s.
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
let SIN = null;
2026-10-03 23:41:48 +02:00
function fillWater(d, len, sr, rate) { // rate = Gruppen pro Sekunde
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
if (!SIN) { SIN = new Float32Array(4096); for (let i = 0; i < 4096 ; i + + ) SIN [ i ] = Math . sin ( i / 4096 * 2 * Math . PI ) ; }
2026-10-03 23:41:48 +02:00
const NC = Math.round(rate * 10), p1 = Math.random() * 6.28, p2 = Math.random() * 6.28, p3 = Math.random() * 6.28;
for (let c = 0; c < NC ; c + + ) {
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
let pos;
do {
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);
} while (Math.random() * 1.95 > lam);
2026-10-03 23:41:48 +02:00
const rc = (1 + 4 * Math.pow(Math.random(), 1.5)) * 1e-3; // typischer Radius der Gruppe: 1– 5 mm
const loud = Math.pow(Math.random(), 2) * 1.2 + .05, m = 2 + Math.floor(Math.random() * 7), span = (.04 + Math.random() * .16) * sr;
for (let b = 0; b < m ; b + + ) {
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
const amp = loud * Math.pow(r / .003, 1.2) * (.5 + Math.random() * .5);
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;
let env = 1, phase = 0;
for (let j = 0; j < n ; j + + ) {
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;
}
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
}
}
}
// Grillen: je eine Grille pro Buffer (Chirp aus 3– 4 Pulsen), Periode teilt die Loop-Länge exakt
const CB = [], CRICKET_BASE = R(4400, 4800); // Chor: alle Tiere nahezu gleiche Tonhöhe (±1 %)
function cricketBuf(i) {
if (CB[i]) return CB[i];
const ctx = S.ctx, sr = ctx.sampleRate, len = sr * 8, buf = ctx.createBuffer(1, len, sr), d = buf.getChannelData(0);
const fc = CRICKET_BASE * (1 + R(-.012, .012)), nCh = Math.floor(8 / R(.36, .42)), P = len / nCh, pulses = 3 + Math.floor(Math.random() * 2), off = Math.random() * P;
for (let k = 0; k < nCh ; k + + ) for ( let p = 0; p < pulses ; p + + ) {
const start = Math.floor(k * P + off + p * sr * .024), n = Math.floor(sr * .016);
for (let j = 0; j < n ; j + + ) d [ ( start + j ) % len ] + = Math . sin ( 2 * Math . PI * fc * j / sr ) * Math . pow ( Math . sin ( Math . PI * j / n ) , 2 ) * ( p = == 0 ? . 7 : 1 ) ;
}
let sum = 0; for (let j = 0; j < len ; j + + ) sum + = d [ j ] * d [ j ] ;
const k = .16 / Math.sqrt(sum / len + 1e-12);
for (let j = 0; j < len ; j + + ) d [ j ] = clamp ( d [ j ] * k , -1 , 1 ) ;
return CB[i] = buf;
}
// Baukasten pro Modus: eigener Ausgang, Quellen, Timer – alles lässt sich sauber ausblenden
function newKit(gain) {
const ctx = S.ctx, K = { out: ctx.createGain(), srcs: [], timers: [], dead: false, a: .5, b: .5, set() {} };
K.out.gain.value = 0; K.out.gain.setTargetAtTime(gain, T(), 1.4);
K.out.connect(S.g.modeBus);
K.src = (buf, dest, rate = 1) => {
const s = ctx.createBufferSource(); s.buffer = buf; s.loop = true; s.playbackRate.value = rate;
s.connect(dest); s.start(0, Math.random() * buf.duration); K.srcs.push(s); return s;
};
K.osc = (type, f, dest) => { const o = ctx.createOscillator(); o.type = type; o.frequency.value = f; o.connect(dest); o.start(); K.srcs.push(o); return o; };
K.every = (fn, min, max, first = .1) => {
const slot = K.timers.length; K.timers.push(0);
const val = x => typeof x === "function" ? x() : x;
const loop = () => { if (K.dead) return; fn(); K.timers[slot] = setTimeout(loop, R(val(min), val(max)) * 1000); };
K.timers[slot] = setTimeout(loop, first * 1000);
};
K.apply = (a, b) => { K.a = a; K.b = b; K.set(a, b); };
K.stop = () => {
K.dead = true; K.timers.forEach(clearTimeout);
K.out.gain.cancelScheduledValues(T()); K.out.gain.setTargetAtTime(0, T(), .7);
setTimeout(() => { K.srcs.forEach(s => { try { s.stop(); } catch (e) {} }); try { K.out.disconnect(); } catch (e) {} }, 4000);
};
return K;
}
// ----- Bauteile der einzelnen Modi -----
function buildColor(K, type) {
const ctx = S.ctx, head = ctx.createGain();
K.src(noiseBuf(type === "grey" ? "white" : type), head);
let n = head;
if (type === "grey") for (const f of [bq("lowshelf", 200, .7, 12), bq("peaking", 3200, .7, -10), bq("highshelf", 9000, .7, 9)]) { n.connect(f); n = f; }
const lp = bq("lowpass", 20000, .5), am = ctx.createGain(), lfoG = ctx.createGain();
n.connect(lp); lp.connect(am).connect(K.out);
K.osc("sine", .11, lfoG); lfoG.connect(am.gain);
K.set = (a, b) => { sm(lp.frequency, 500 * Math.pow(40, a), .4); const d = b * .8; sm(am.gain, 1 - d / 2); sm(lfoG.gain, d / 2); };
}
function buildRain(K, storm) {
const ctx = S.ctx, gl = ctx.createGain(), gh = ctx.createGain(), lp = bq("lowpass", 9000, .5);
const wash = ctx.createGain(), hp = bq("highpass", 350, .5), wl = bq("lowpass", 7000, .5);
K.src(dropBuf("rain", 60), gl); K.src(dropBuf("rain", 320), gh); gl.connect(lp); gh.connect(lp);
K.src(noiseBuf("pink"), hp); hp.connect(wl).connect(wash).connect(lp); lp.connect(K.out);
K.set = (a, b) => {
sm(gl.gain, 1 - a); sm(gh.gain, a * 1.1); sm(wash.gain, .05 + .25 * a);
sm(lp.frequency, storm ? 7000 : 2500 * Math.pow(5.5, b), .6);
};
if (storm) K.every(() => thunder(K), () => 9 / (.3 + K.a), () => 32 / (.3 + K.a), 4);
}
function thunder(K) {
const ctx = S.ctx, near = K.b, d = (1 - near) * 5 + .3 + Math.random() * 1.2, t0 = T() + d;
S.flash = .4 + .6 * near; setTimeout(() => { if (!K.dead) S.flash = Math.max(S.flash, .3 + .5 * near); }, 140);
const dur = R(6, 10), lp = bq("lowpass", 100 + 220 * near, .7), g = ctx.createGain(), s = ctx.createBufferSource();
s.buffer = noiseBuf("brown"); s.connect(lp).connect(g).connect(K.out);
const peak = .9 * (.35 + .65 * near);
lp.frequency.setValueAtTime(100 + 220 * near, t0); lp.frequency.exponentialRampToValueAtTime(60, t0 + dur);
g.gain.setValueAtTime(0, t0); g.gain.linearRampToValueAtTime(peak, t0 + .35);
let tt = t0 + .35, amp = peak;
for (let i = 0; i < 4 ; i + + ) { tt + = R ( . 8 , 1 . 8 ) ; amp * = R ( . 45 , . 85 ) ; g . gain . linearRampToValueAtTime ( amp * R ( . 6 , 1 ) , tt ) ; }
g.gain.linearRampToValueAtTime(0, t0 + dur);
s.start(t0, R(0, .5), dur + .2);
const cb = bq("bandpass", R(900, 2500), .8), cg = ctx.createGain(), cs = ctx.createBufferSource();
cs.buffer = noiseBuf("white"); cs.connect(cb).connect(cg).connect(K.out);
cg.gain.setValueAtTime(0, t0); cg.gain.linearRampToValueAtTime(.7 * near * near, t0 + .01); cg.gain.exponentialRampToValueAtTime(.001, t0 + .4);
cs.start(t0, R(0, 3), .5);
}
function buildOcean(K) {
const ctx = S.ctx, lp = bq("lowpass", 220, .6), bg = ctx.createGain(), hp = bq("highpass", 1800, .5), fl = bq("lowpass", 7500, .5), fg = ctx.createGain();
K.src(noiseBuf("brown"), lp); lp.connect(bg).connect(K.out); bg.gain.value = .12;
K.src(noiseBuf("white"), hp); hp.connect(fl).connect(fg).connect(K.out); fg.gain.value = 0;
const bed = ctx.createGain(), bl = bq("lowpass", 900, .5); bed.gain.value = .06; K.src(noiseBuf("pink"), bl); bl.connect(bed).connect(K.out);
K.waveEnd = T() + .1;
const sched = () => {
if (K.dead) return;
const a = K.a, b = K.b, Tw = R(7, 12) * (1.25 - .35 * a), A = (.35 + .65 * a) * R(.6, 1), t0 = K.waveEnd, rise = Tw * .42;
bg.gain.linearRampToValueAtTime(.12, t0); bg.gain.linearRampToValueAtTime(A, t0 + rise); bg.gain.linearRampToValueAtTime(.12, t0 + Tw);
lp.frequency.linearRampToValueAtTime(220, t0); lp.frequency.linearRampToValueAtTime(350 + 900 * b + 500 * A, t0 + rise); lp.frequency.linearRampToValueAtTime(220, t0 + Tw);
fg.gain.linearRampToValueAtTime(0, t0 + rise * .6); fg.gain.linearRampToValueAtTime(A * .55 * (.25 + b), t0 + rise * 1.15); fg.gain.linearRampToValueAtTime(0, t0 + Tw * .98);
K.waveEnd = t0 + Tw;
K.timers[0] = setTimeout(sched, Math.max(300, (K.waveEnd - T() - 2.5) * 1000));
};
K.timers.push(0); sched();
}
function buildWind(K) {
const ctx = S.ctx, bp = bq("bandpass", 500, 1.2), g = ctx.createGain(), wb = bq("bandpass", 900, 22), wg = ctx.createGain(), low = bq("lowpass", 250, .5), lg = ctx.createGain();
g.gain.value = 0; wg.gain.value = 0; lg.gain.value = .3;
K.src(noiseBuf("pink"), bp); bp.connect(g).connect(K.out);
K.src(noiseBuf("white"), wb); wb.connect(wg).connect(K.out);
K.src(noiseBuf("brown"), low); low.connect(lg).connect(K.out);
K.set = a => sm(lg.gain, .15 + .6 * a, 1);
K.every(() => {
const a = K.a, b = K.b;
sm(bp.frequency, R(250, 1100) * (.6 + .8 * a), 2.5); sm(bp.Q, R(.7, 2.2), 2.5);
sm(g.gain, (.12 + R(0, .8) * a) * .9, 2.2);
sm(wb.frequency, R(600, 1800), 3); sm(wg.gain, b * R(.3, 1) * 10, 2.5);
}, 2.5, 6, .1);
}
function buildStream(K) {
2026-10-03 23:41:48 +02:00
const ctx = S.ctx, g1 = ctx.createGain(), g2 = ctx.createGain(), hp = bq("highpass", 250, .5);
const s1 = K.src(dropBuf("water", 12), g1), s2 = K.src(dropBuf("water", 45), g2); // ruhig / sprudelnd
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
g1.connect(hp); g2.connect(hp);
2026-10-03 23:41:48 +02:00
const sp = bq("highpass", 1800, .5), spl = bq("lowpass", 5000, .5), spg = ctx.createGain(); // Spritzer
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);
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
hp.connect(K.out);
K.set = (a, b) => {
2026-10-03 23:41:48 +02:00
sm(g1.gain, 1 - a * .7); sm(g2.gain, a * 1.3); sm(spg.gain, .02 + .07 * a);
const rt = .8 + .5 * b; sm(s1.playbackRate, rt); sm(s2.playbackRate, rt * 1.03);
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
};
2026-10-03 23:41:48 +02:00
K.every(() => { sm(fl.frequency, R(500, 1800), .4); sm(fg.gain, R(.01, .07) * (.4 + K.a), .2); }, .4, 1.2);
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
}
function buildFire(K) {
const ctx = S.ctx, rl = bq("lowpass", 450, .6), rg = ctx.createGain(), hp = bq("highpass", 3500, .5), hg = ctx.createGain();
const g1 = ctx.createGain(), g2 = ctx.createGain(), ph = bq("highpass", 600, .5);
K.src(noiseBuf("brown"), rl); rl.connect(rg).connect(K.out); rg.gain.value = .5;
K.src(noiseBuf("pink"), hp); hp.connect(hg).connect(K.out); hg.gain.value = .03;
K.src(dropBuf("pop", 12), g1); K.src(dropBuf("pop", 45), g2); g1.connect(ph); g2.connect(ph); ph.connect(K.out);
K.set = (a, b) => { sm(g1.gain, (1 - b) * (.6 + .5 * a)); sm(g2.gain, b * (.6 + .5 * a)); };
K.every(() => sm(rg.gain, (.2 + .6 * K.a) * R(.55, 1), .35), .3, 1.2);
}
function buildCrickets(K) {
const ctx = S.ctx, cs = [];
for (let i = 0; i < 6 ; i + + ) {
const g = ctx.createGain(), pn = ctx.createStereoPanner(); g.gain.value = 0; pn.pan.value = R(-.85, .85);
const src = K.src(cricketBuf(i), g); g.connect(pn).connect(K.out); cs.push({ g, src });
}
const bed = ctx.createGain(), lp = bq("lowpass", 220, .5); bed.gain.value = .05; K.src(noiseBuf("brown"), lp); lp.connect(bed).connect(K.out);
K.set = (a, b) => { const n = 1 + a * 5; cs.forEach((c, i) => { sm(c.g.gain, clamp(n - i, 0, 1) * .8, 1.2); sm(c.src.playbackRate, .8 + .45 * b, 1); }); };
}
function buildFan(K, cabin) {
const ctx = S.ctx, lp = bq("lowpass", 900, .6), g = ctx.createGain(), hp = bq("highpass", 2000, .5), lp2 = bq("lowpass", 6000, .5), hg = ctx.createGain(), hg2 = ctx.createGain();
g.gain.value = 1; hg.gain.value = cabin ? .035 : .05; hg2.gain.value = 0;
K.src(noiseBuf("brown"), lp); lp.connect(g).connect(K.out);
K.src(noiseBuf("pink"), hp); hp.connect(lp2).connect(hg).connect(K.out);
const o1 = K.osc("sine", cabin ? 83 : 100, hg2), o2 = K.osc("sine", cabin ? 83.8 : 200, hg2), o3 = K.osc("sine", cabin ? 166 : 300, hg2);
hg2.connect(K.out);
K.set = (a, b) => {
if (cabin) { sm(lp.frequency, 400 + 1400 * a); sm(hg2.gain, .02 + .12 * b); }
else { sm(lp.frequency, 500 + 1500 * a); sm(o1.frequency, 70 + 90 * a); sm(o2.frequency, 140 + 180 * a); sm(o3.frequency, 210 + 270 * a); sm(hg2.gain, .01 + .1 * b); }
};
}
function buildBinaural(K) {
const ctx = S.ctx, l = ctx.createGain(), r = ctx.createGain(), l2 = ctx.createGain(), r2 = ctx.createGain();
const pl = ctx.createStereoPanner(), pr = ctx.createStereoPanner(); pl.pan.value = -1; pr.pan.value = 1;
l.gain.value = r.gain.value = .16; l2.gain.value = r2.gain.value = .04;
l.connect(pl); l2.connect(pl); r.connect(pr); r2.connect(pr); pl.connect(K.out); pr.connect(K.out);
const oL = K.osc("sine", 200, l), oR = K.osc("sine", 210, r), oL2 = K.osc("sine", 400, l2), oR2 = K.osc("sine", 410, r2);
const bed = ctx.createGain(), lp = bq("lowpass", 1800, .5); bed.gain.value = .22; K.src(noiseBuf("pink"), lp); lp.connect(bed).connect(K.out);
K.set = (a, b) => {
const beat = Math.pow(40, a), f0 = 100 * Math.pow(4, b);
sm(oL.frequency, f0 - beat / 2, .3); sm(oR.frequency, f0 + beat / 2, .3);
sm(oL2.frequency, 2 * f0 - beat / 2, .3); sm(oR2.frequency, 2 * f0 + beat / 2, .3);
};
}
function buildBowls(K) {
const ctx = S.ctx, ratios = [1, 2.71, 5.15, 8.2], amps = [1, .45, .22, .1], decs = [16, 10, 6, 3.5];
const strike = () => {
const t = T() + .05, midi = pick([48, 50, 52, 55, 57], Math.random) + 12 * Math.round(K.b * 1.5), f = mtof(midi);
const pn = ctx.createStereoPanner(); pn.pan.value = R(-.6, .6); pn.connect(K.out);
ratios.forEach((ra, i) => [-R(.3, 1.1), R(.3, 1.1)].forEach(det => {
const o = ctx.createOscillator(), g = ctx.createGain(), dec = decs[i] * R(.8, 1.2);
o.frequency.value = f * ra + det;
g.gain.setValueAtTime(0, t); g.gain.linearRampToValueAtTime(amps[i] * .07, t + .008); g.gain.exponentialRampToValueAtTime(.0001, t + dec);
o.connect(g).connect(pn); o.start(t); o.stop(t + dec + .1);
}));
};
K.every(strike, () => 4 / (.35 + K.a * 1.4), () => 9 / (.35 + K.a * 1.4), .3);
}
function buildWomb(K) {
const ctx = S.ctx, lp = bq("lowpass", 500, .5), g = ctx.createGain(), sw = bq("bandpass", 900, .4), swg = ctx.createGain(), tl = bq("lowpass", 160, .7);
g.gain.value = .55; swg.gain.value = .12;
K.src(noiseBuf("brown"), lp); lp.connect(g).connect(K.out);
K.src(noiseBuf("pink"), sw); sw.connect(swg).connect(K.out); tl.connect(K.out);
const thump = (t, f0, amp) => {
const o = ctx.createOscillator(), og = ctx.createGain();
o.frequency.setValueAtTime(f0, t); o.frequency.exponentialRampToValueAtTime(f0 * .55, t + .14);
og.gain.setValueAtTime(0, t); og.gain.linearRampToValueAtTime(amp, t + .012); og.gain.exponentialRampToValueAtTime(.001, t + .25);
o.connect(og).connect(tl); o.start(t); o.stop(t + .3);
};
const per = () => 60 / (55 + K.a * 60);
2026-10-04 00:18:06 +02:00
// Schläge der nächsten Sekunden vorplanen: ein stockender Hauptthread lässt so keinen Schlag aus
K.nextBeat = T() + .3;
K.every(() => {
while (K.nextBeat < T ( ) + 2 . 5 ) { const t = K.nextBeat; thump ( t , 64 , . 9 ) ; thump ( t + . 27 , 72 , . 6 ) ; K . nextBeat + = per ( ) * R ( 1 , 1 . 01 ) ; }
}, .25, .25, .05);
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
K.set = (a, b) => { sm(tl.frequency, 100 + 300 * b); sm(lp.frequency, 300 + 500 * b); };
}
2026-10-04 00:24:43 +02:00
// Zugfahrt: Rollgeräusch + Schienenstöße. Jeder Stoß wird von zwei Achsen kurz nacheinander angeschlagen
// (Achsabstand 3,1 m, Schienenlänge 33 m) -> "klack-klack ......... klack-klack", Pause ≈ 11× Paarabstand, Takt folgt der Geschwindigkeit.
2026-10-04 00:28:44 +02:00
function railClack(K, t, amp, pitch = 1) {
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
const ctx = S.ctx, nb = noiseBuf("white");
2026-10-04 00:28:44 +02:00
const s1 = ctx.createBufferSource(), bp = bq("bandpass", R(300, 650) * (.7 + .8 * K.b) * pitch, .8), g1 = ctx.createGain(); // dumpfer Anschlag
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
s1.buffer = nb; s1.connect(bp).connect(g1).connect(K.out);
2026-10-04 00:24:43 +02:00
g1.gain.setValueAtTime(0, t); g1.gain.linearRampToValueAtTime(amp * .55, t + .004); g1.gain.exponentialRampToValueAtTime(.001, t + .08);
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
s1.start(t, R(0, 8), .1);
2026-10-04 00:24:43 +02:00
const o = ctx.createOscillator(), og = ctx.createGain(); // Schlag im Wagenboden
2026-10-04 00:28:44 +02:00
o.frequency.setValueAtTime(95 * pitch, t); o.frequency.exponentialRampToValueAtTime(48 * pitch, t + .12);
2026-10-04 00:24:43 +02:00
og.gain.setValueAtTime(0, t); og.gain.linearRampToValueAtTime(amp * .8, t + .005); og.gain.exponentialRampToValueAtTime(.001, t + .24);
o.connect(og).connect(K.out); o.start(t); o.stop(t + .3);
// Karosserie klingt nach: zwei Resonanzmoden, lang abklingend
2026-10-04 00:28:44 +02:00
const f1 = R(170, 220) * pitch;
2026-10-04 00:24:43 +02:00
[[f1, 1.5, .65], [f1 * 1.9, .7, .45]].forEach(([f, a, dec]) => {
const s2 = ctx.createBufferSource(), rb = bq("bandpass", f, 10), g2 = ctx.createGain();
s2.buffer = nb; s2.connect(rb).connect(g2).connect(K.out);
g2.gain.setValueAtTime(0, t); g2.gain.linearRampToValueAtTime(amp * a, t + .006); g2.gain.exponentialRampToValueAtTime(.001, t + dec);
s2.start(t, R(0, 8), dec + .1);
});
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
}
function buildTrain(K) {
const ctx = S.ctx, lp = bq("lowpass", 700, .6), g = ctx.createGain(), bp = bq("bandpass", 900, .5), bg = ctx.createGain();
const hum = ctx.createGain(), sway = ctx.createGain(), swayG = ctx.createGain();
g.gain.value = .45; bg.gain.value = .12; hum.gain.value = .02;
K.src(noiseBuf("brown"), lp); lp.connect(g).connect(sway).connect(K.out);
K.src(noiseBuf("pink"), bp); bp.connect(bg).connect(sway);
const ho = K.osc("sine", 120, hum); hum.connect(K.out);
sway.gain.value = .9; K.osc("sine", .23, swayG); swayG.gain.value = .08; swayG.connect(sway.gain); // leichtes Schaukeln
K.v = 25; K.nextJoint = T() + .3; K.nextPoints = T() + R(25, 50);
K.set = (a, b) => {
K.v = 14 + 36 * a; // 50 … 180 km/h
sm(lp.frequency, 350 + 450 * a + 700 * b, 1); sm(bg.gain, .03 + .1 * a * (.4 + b), 1); sm(ho.frequency, 70 + 120 * a, 1); sm(hum.gain, .015 + .03 * a, 1);
};
K.every(() => {
const now = T();
2026-10-04 00:18:06 +02:00
while (K.nextJoint < now + 2 . 5 ) { / / alle Achsen der Schienenstöße der nächsten Sekunden vorplanen ( ein stockender Hauptthread reißt so keine Lücke )
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
const v = K.v, t0 = K.nextJoint, amp = .8 * (.85 + Math.random() * .3);
2026-10-04 00:28:44 +02:00
[0, 3.1 / v].forEach((o, i) => railClack(K, t0 + o, amp * (i ? .9 : 1), i ? 1.12 : 1)); // zwei Achsen des Drehgestells, dann Pause bis zum nächsten Stoß
2026-10-04 00:24:43 +02:00
K.nextJoint += 33 / v;
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
}
if (now > K.nextPoints) { // Weiche: kurzes schnelles Klapperbündel
const n = 7 + Math.floor(Math.random() * 5);
for (let i = 0; i < n ; i + + ) railClack ( K , now + . 2 + i * R ( . 07 , . 13 ) , R ( . 5 , . 9 ) ) ;
K.nextPoints = now + R(40, 90);
}
}, .12, .12, .05);
}
// ----- Modus-Tabelle -----
const bandOf = hz => hz < 4 ? " Delta " : hz < 8 ? " Theta " : hz < 13 ? " Alpha " : hz < 30 ? " Beta " : " Gamma " ;
const binInfo = (a, b) => { const hz = Math.pow(40, a); return `${hz.toFixed(1)} Hz · ${bandOf(hz)} · Träger ${Math.round(100 * Math.pow(4, b))} Hz · Kopfhörer!`; };
const colorDesc = "Regler: Höhen-Dämpfung und langsames Atmen der Lautstärke.";
const MODES = {
ambient: { group: "Musik", name: "Ambient-Musik", music: true, hue: 220 },
white: { group: "Rauschfarben", name: "Weißes Rauschen", desc: "Gleiche Energie in allen Frequenzen – hell und rauschig, maskiert Geräusche gut. " + colorDesc, labels: ["Höhen-Dämpfung", "Atmen"], def: [.9, 0], gain: 0.3, hue: 215, vis: "float", build: K => buildColor(K, "white") },
pink: { group: "Rauschfarben", name: "Pinkes Rauschen", desc: "Nimmt pro Oktave um 3 dB ab – ausgewogen, wie Regen und Blätterrauschen. " + colorDesc, labels: ["Höhen-Dämpfung", "Atmen"], def: [.9, 0], gain: 0.42, hue: 330, vis: "float", build: K => buildColor(K, "pink") },
brown: { group: "Rauschfarben", name: "Braunes Rauschen", desc: "Tief und dumpf (− 6 dB pro Oktave), wie Donnergrollen oder ein Wasserfall. " + colorDesc, labels: ["Höhen-Dämpfung", "Atmen"], def: [.9, 0], gain: 0.6, hue: 28, vis: "float", build: K => buildColor(K, "brown") },
blue: { group: "Rauschfarben", name: "Blaues Rauschen", desc: "Zu den Höhen ansteigend, zischend und klar. " + colorDesc, labels: ["Höhen-Dämpfung", "Atmen"], def: [.9, 0], gain: 0.25, hue: 205, vis: "float", build: K => buildColor(K, "blue") },
violet: { group: "Rauschfarben", name: "Violettes Rauschen", desc: "Noch stärker zu den Höhen – sehr hell, fast nur Zischen. " + colorDesc, labels: ["Höhen-Dämpfung", "Atmen"], def: [.8, 0], gain: 0.25, hue: 275, vis: "float", build: K => buildColor(K, "violet") },
grey: { group: "Rauschfarben", name: "Graues Rauschen", desc: "Weißes Rauschen, an die Ohrempfindlichkeit angepasst – wirkt über alle Höhen gleich laut. " + colorDesc, labels: ["Höhen-Dämpfung", "Atmen"], def: [.9, 0], gain: 0.2, hue: 190, vis: "float", build: K => buildColor(K, "grey") },
rain: { group: "Natur", name: "Regen", desc: "Tausende einzeln synthetisierte Tropfen auf einem Rauschteppich.", labels: ["Stärke", "Dumpf ↔ Hell"], def: [.5, .55], gain: 1.3, hue: 210, vis: "rain", build: K => buildRain(K, false) },
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 },
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 },
2026-10-03 23:41:48 +02:00
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 },
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
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 },
fan: { group: "Technik", name: "Ventilator", desc: "Gleichmäßiger Luftstrom mit tiefem Motorbrummen.", labels: ["Drehzahl", "Brummen"], def: [.4, .35], gain: 0.3, hue: 200, vis: "float", build: K => buildFan(K, false) },
cabin: { group: "Technik", name: "Flugzeugkabine", desc: "Dumpfes Rauschen und tiefes Triebwerksdröhnen – beliebt zum Einschlafen.", labels: ["Dumpf ↔ offen", "Triebwerk"], def: [.3, .5], gain: 0.27, hue: 215, vis: "float", build: K => buildFan(K, true) },
train: { group: "Technik", name: "Zugfahrt", desc: "Gleichmäßiges Rollen mit dem Tschuck-tschuck der Schienenstöße, ab und zu rattert eine Weiche.", labels: ["Geschwindigkeit", "Abteil dumpf ↔ Fenster offen"], def: [.4, .35], gain: .45, hue: 35, vis: "wind", info: a => Math.round((14 + 36 * a) * 3.6) + " km/h", build: buildTrain },
delta: { group: "Fokus & Schlaf", name: "Binaurale Beats: Delta", desc: "Zwei leicht verstimmte Sinustöne links und rechts, Differenz ≈ 2 Hz (Tiefschlaf).", labels: ["Beat-Frequenz", "Trägerton"], def: [.19, .35], gain: 0.45, hue: 250, vis: "float", info: binInfo, build: buildBinaural },
theta: { group: "Fokus & Schlaf", name: "Binaurale Beats: Theta", desc: "Differenz ≈ 6 Hz (Meditation, Dämmerzustand).", labels: ["Beat-Frequenz", "Trägerton"], def: [.49, .35], gain: 0.45, hue: 280, vis: "float", info: binInfo, build: buildBinaural },
alpha: { group: "Fokus & Schlaf", name: "Binaurale Beats: Alpha", desc: "Differenz ≈ 10 Hz (entspannte Wachheit).", labels: ["Beat-Frequenz", "Trägerton"], def: [.62, .35], gain: 0.45, hue: 160, vis: "float", info: binInfo, build: buildBinaural },
beta: { group: "Fokus & Schlaf", name: "Binaurale Beats: Beta", desc: "Differenz ≈ 18 Hz (Konzentration).", labels: ["Beat-Frequenz", "Trägerton"], def: [.78, .35], gain: 0.45, hue: 45, vis: "float", info: binInfo, build: buildBinaural },
bowls: { group: "Fokus & Schlaf", name: "Klangschalen", desc: "Angeschlagene Schalen mit schwebenden Obertönen und langem Nachklang.", labels: ["Häufigkeit", "Tonhöhe"], def: [.4, .35], gain: 1.4, hue: 40, rev: .9, vis: "float", build: buildBowls },
womb: { group: "Fokus & Schlaf", name: "Herzschlag", desc: "Gedämpfter Herzschlag in tiefem Rauschen – wie im Mutterleib.", labels: ["Herzschlag-Tempo", "Dumpf ↔ nah"], def: [.2, .4], gain: 0.25, hue: 5, rev: .15, vis: "float", build: buildWomb },
};
function applyRev() {
if (!S.g) return;
const M = MODES[S.mode];
S.g.modeRev.gain.setTargetAtTime(M.music ? 0 : (M.rev || 0) * S.verb * 1.2, S.ctx.currentTime, .3);
2026-10-04 00:18:06 +02:00
if (!LITE) return;
// Telefone: Was gerade nicht klingt, vom Ausgang trennen – der Browser rechnet es dann gar nicht
// (Musik-Bett, Delay, Hall ohne Hall-Modus). Musik geht nach dem Ausblenden weg, damit nichts knackt.
const music = !!M.music;
if (music) linkMusic(true);
else setTimeout(() => { if (!MODES[S.mode].music) linkMusic(false); }, 1600);
const rev = music || !!M.rev;
if (S.revLinked !== rev) {
S.revLinked = rev;
try { rev ? S.g.conv.connect(S.g.mix) : S.g.conv.disconnect(S.g.mix); } catch (e) {}
}
}
function linkMusic(on) {
if (S.musicLinked === on) return;
S.musicLinked = on;
const G = S.g;
for (const [a, b] of [[G.dry, G.mix], [G.send, G.conv], [G.noiseBus, G.mix], [G.nSend, G.conv], [G.dlp, G.conv], [G.dlp, G.mix]]) {
try { on ? a.connect(b) : a.disconnect(b); } catch (e) {}
}
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
}
function startSoundscape() {
const M = MODES[S.mode], K = newKit(M.gain || .7);
S.modeObj = K; M.build(K); K.apply(S.mp.a, S.mp.b); applyRev();
}
function tickMode() { // „Wandel": die beiden Regler wandern langsam von selbst
const K = S.modeObj; if (!K) return;
const t = S.ctx.currentTime, amp = S.drift * .3;
const w1 = Math.sin(t * .031 + 1) * .5 + Math.sin(t * .083 + 4) * .5, w2 = Math.sin(t * .027 + 2) * .5 + Math.sin(t * .067 + 5) * .5;
K.ea = clamp(S.mp.a + amp * w1, 0, 1); K.eb = clamp(S.mp.b + amp * w2, 0, 1);
K.apply(K.ea, K.eb); updateInfo();
}
function leaveMusic() {
S.timers.forEach(clearTimeout); S.timers = [];
const G = S.g, t = S.ctx.currentTime;
G.voiceBus.gain.setTargetAtTime(0, t, .7); G.grainBus.gain.setTargetAtTime(0, t, .7);
G.windGain.gain.setTargetAtTime(0, t, .7); G.seaGain.gain.setTargetAtTime(0, t, .7);
}
function enterMode() {
if (MODES[S.mode].music) {
const G = S.g, t = S.ctx.currentTime;
G.voiceBus.gain.setTargetAtTime(1, t, .3); G.grainBus.gain.setTargetAtTime(1, t, .3);
startMusic();
} else startSoundscape();
applyRev();
}
2026-10-03 23:08:57 +02:00
// Kommunikation mit der einbettenden Seite (diora): Radio/Podcast ↔ Ambient
function post(state) {
try { if (window.parent !== window) window.parent.postMessage({ type: "ambient", state }, "*"); } catch (e) {}
}
addEventListener("message", e => {
if (e.source !== window.parent) return;
const d = e.data;
if (!d || d.type !== "ambient") return;
if (d.cmd === "start" & & !S.running) start();
else if (d.cmd === "stop" & & S.running) stop();
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
else if (d.cmd === "mode" & & MODES[d.mode]) setMode(d.mode);
2026-10-04 00:01:03 +02:00
else if (d.cmd === "volume" & & isFinite(d.value)) { // die einbettende Seite (diora) gibt die Lautstärke vor
S.hostVol = clamp(+d.value, 0, 1);
if (S.running) S.g.master.gain.setTargetAtTime(outVol(), S.ctx.currentTime, .1);
}
2026-10-03 23:08:57 +02:00
});
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
function startMusic() {
2026-10-03 23:08:57 +02:00
resetScene();
drone(); playChord();
later(playChord, 10, 18, 1);
later(playBells, 2, 6, 2);
later(changeDegree, 18, 34, 3);
later(wanderTimbre, 15, 30, 4);
later(() => changeKey(true), 90, 180, 5);
later(drone, 40, 70, 6);
later(grainCloud, 3, 8, 7);
later(snapGrains, 20, 30, 8, .3);
startLoops();
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
}
function start() {
if (!S.ctx) buildGraph();
S.running = true; S.timers = [];
// Ohne Nutzergeste (z. B. Befehl von außen) kann der Browser den Ton blockieren -> dann ehrlich "stopped" melden
S.ctx.resume().then(() => { if (S.running & & S.ctx.state !== "running") stop(); }, () => { if (S.running) stop(); });
const t = S.ctx.currentTime;
S.g.master.gain.cancelScheduledValues(t);
2026-10-04 00:01:03 +02:00
S.g.master.gain.setTargetAtTime(outVol(), t, 2.5);
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
S.tickId = setInterval(tick, 1000);
enterMode();
2026-10-03 23:08:57 +02:00
$("toggle").textContent = "Stopp";
wake(); post("playing");
}
function stop() {
S.running = false;
S.timers.forEach(t => { clearTimeout(t); clearInterval(t); }); S.timers = [];
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
clearInterval(S.tickId);
if (S.modeObj) { S.modeObj.stop(); S.modeObj = null; }
2026-10-03 23:08:57 +02:00
const t = S.ctx.currentTime;
S.g.master.gain.cancelScheduledValues(t);
S.g.master.gain.setTargetAtTime(0, t, 1.5);
setTimeout(() => { if (!S.running) S.ctx.suspend(); }, 12000);
$("toggle").textContent = "Start";
S.visUntil = performance.now() + 14000; // Partikel noch ausklingen lassen, danach Zeichnen stoppen
post("stopped");
}
function reroll() { // neue Szene, laufende Stimmen klingen aus
resetScene();
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
if (S.running & & MODES[S.mode].music) { drone(); playChord(); }
2026-10-03 23:08:57 +02:00
}
// ---------- Seed / Link ----------
function writeHash() {
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
try { history.replaceState(null, "", "#seed=" + encodeURIComponent(S.seedText) + "& p=" + encodeURIComponent(S.presetName) + "& m=" + encodeURIComponent(S.mode)); } catch (e) {}
2026-10-03 23:08:57 +02:00
}
function applySeed(text) {
S.seedText = text.trim() || String(Math.floor(Math.random() * 900000) + 100000);
S.seed = toSeed(S.seedText); $("seed").value = S.seedText; writeHash();
if (S.ctx) reroll(); else seedAll(S.seed);
}
function applyPreset(name) {
if (!PRESETS[name]) name = "Frei";
S.presetName = name; S.P = PRESETS[name]; $("preset").value = name; writeHash();
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
if (S.running & & MODES[S.mode].music) { wanderTimbre(); changeKey(false); }
2026-10-03 23:08:57 +02:00
}
// ---------- UI ----------
const METERS = [
["Helligkeit", () => S.cur.bright], ["FM", () => S.cur.fm], ["Säge", () => S.cur.saw], ["Wavetable", () => S.cur.wt],
["Rauschen", () => S.cur.noise], ["Körner", () => S.cur.grain], ["Bewegung", () => S.cur.motion * 1.6], ["Spannung", () => eff()],
];
$("meters").innerHTML = METERS.map(([l], i) =>
`< div > < div class = "mlabel" > ${l}< / div > < div class = "meter" > < i id = "m${i}" > < / i > < / div > < / div > `).join("");
function renderMeters() { METERS.forEach(([, fn], i) => $("m" + i).style.width = clamp(fn() * 100, 2, 100) + "%"); }
const ROMAN = ["I","II","III","IV","V","VI","VII","VIII"];
function arcLabel() {
const p = arcPhase();
return p < .15 ? " Ruhe " : p < . 4 ? " Aufbau " : p < . 6 ? " Höhepunkt " : p < . 85 ? " Rückzug " : " Ruhe " ;
}
function updateInfo() {
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
const M = MODES[S.mode];
if (!M.music) {
const K = S.modeObj;
$("key").textContent = M.name;
$("chord").innerHTML = M.desc + (M.info ? ` < b > ${M.info(K & & K.ea != null ? K.ea : S.mp.a, K & & K.eb != null ? K.eb : S.mp.b)}< / b > ` : "");
return;
}
if (!S.running) { $("key").textContent = "– "; $("chord").textContent = "Noch still."; return; }
2026-10-03 23:08:57 +02:00
$("key").textContent = `${NOTE[S.root % 12]} ${S.scaleName}`;
$("chord").innerHTML = `Stufe < b > ${ROMAN[S.degree] || S.degree + 1}< / b > · ${S.presetName} · ${S.tuning === "just" ? "rein" : "gleichstufig"} · Bogen: < b > ${arcLabel()}< / b > `;
}
$("preset").innerHTML = Object.keys(PRESETS).map(n => `< option > ${n}< / option > `).join("");
$("toggle").onclick = () => S.running ? stop() : start();
2026-10-04 00:01:03 +02:00
$("vol").oninput = () => { if (S.running) S.g.master.gain.setTargetAtTime(outVol(), S.ctx.currentTime, .1); };
2026-10-03 23:08:57 +02:00
$("density").oninput = e => S.density = +e.target.value;
$("drift").oninput = e => S.drift = +e.target.value;
$("arc").oninput = e => S.arcDepth = +e.target.value;
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
$("verb").oninput = e => { S.verb = +e.target.value; if (S.g) { S.g.send.gain.setTargetAtTime(S.verb * 1.2, S.ctx.currentTime, .2); applyRev(); } };
2026-10-03 23:08:57 +02:00
$("tuning").onchange = e => { S.tuning = e.target.value; updateInfo(); };
$("preset").onchange = e => applyPreset(e.target.value);
$("seed").onchange = e => applySeed(e.target.value);
$("reroll").onclick = () => { applySeed(String(Math.floor(Math.random() * 900000) + 100000)); if (!S.running) start(); };
$("share").onclick = async () => {
writeHash();
try { await navigator.clipboard.writeText(location.href); $("share").textContent = "Kopiert ✓"; }
catch (e) { $("share").textContent = "Adresszeile kopieren"; }
setTimeout(() => $("share").textContent = "Link kopieren", 1800);
};
S.density = +$("density").value; S.drift = +$("drift").value; S.verb = +$("verb").value; S.arcDepth = +$("arc").value;
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
// Modus-Auswahl
$("mode").innerHTML = (() => {
const groups = {};
for (const [k, m] of Object.entries(MODES)) (groups[m.group] = groups[m.group] || []).push(`< option value = "${k}" > ${m.name}< / option > `);
return Object.entries(groups).map(([g, o]) => `< optgroup label = "${g}" > ${o.join("")}< / optgroup > `).join("");
})();
function applyModeUI() {
const M = MODES[S.mode], root = document.documentElement;
root.classList.toggle("noise", !M.music); root.classList.toggle("rev", !!M.rev);
if (!M.music) { $("lla").textContent = M.labels[0]; $("llb").textContent = M.labels[1]; $("la").value = S.mp.a; $("lb").value = S.mp.b; }
$("driftL").textContent = M.music ? "Wandel-Tempo" : "Schwankung";
applyRev();
}
function setMode(name) {
if (!MODES[name]) name = "ambient";
const was = MODES[S.mode], M = MODES[name];
S.mode = name; $("mode").value = name;
if (!M.music) S.mp = { a: M.def[0], b: M.def[1] };
applyModeUI(); writeHash();
if (S.running) {
if (was.music) leaveMusic();
if (S.modeObj) { S.modeObj.stop(); S.modeObj = null; }
enterMode();
}
updateInfo(); wake();
}
$("mode").onchange = e => setMode(e.target.value);
$("la").oninput = e => { S.mp.a = +e.target.value; if (S.modeObj) S.modeObj.apply(S.mp.a, S.mp.b); };
$("lb").oninput = e => { S.mp.b = +e.target.value; if (S.modeObj) S.modeObj.apply(S.mp.a, S.mp.b); };
// Startwerte aus dem Link (#seed=…& p=…& m=…)
2026-10-03 23:08:57 +02:00
{
const q = new URLSearchParams(location.hash.slice(1));
applyPreset(q.get("p") || "Frei");
applySeed(q.get("seed") || "");
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
setMode(q.get("m") || "ambient");
2026-10-03 23:08:57 +02:00
}
// ---------- Visualisierung ----------
const cv = $("viz"), cx = cv.getContext("2d");
S.visUntil = performance.now() + 1500;
function wake() { if (!rafId) rafId = requestAnimationFrame(draw); }
function resize() {
2026-10-04 00:09:28 +02:00
cv.width = Math.round(cv.clientWidth * DPR());
cv.height = Math.round(cv.clientHeight * DPR());
2026-10-03 23:08:57 +02:00
wake();
}
resize();
new ResizeObserver(resize).observe(document.documentElement); // auch beim Wieder-Einblenden eines versteckten iframes
document.addEventListener("visibilitychange", wake);
let hueV = 220; const parts = [];
function spawnParticles(ev) {
const W = cv.width, H = cv.height;
const x = W * clamp((Math.log2(ev.f) - 5) / 7.2, .02, .98);
const count = ev.kind === "bell" ? 6 : Math.round(3 + ev.dur / 5);
const keyHue = (S.root % 12) * 30 + 200;
for (let i = 0; i < count & & parts . length < 450 ; i + + ) {
parts.push({
2026-10-04 00:09:28 +02:00
x: x + rnd(-40, 40, Math.random) * DPR(), y: H * rnd(.55, .85, Math.random),
2026-10-03 23:08:57 +02:00
vx: rnd(-.12, .12, Math.random), vy: -rnd(.08, .4, Math.random) * (ev.kind === "bell" ? 1.6 : 1),
2026-10-04 00:09:28 +02:00
r: (ev.kind === "bell" ? rnd(4, 9, Math.random) : rnd(10, 30, Math.random)) * DPR(),
2026-10-03 23:08:57 +02:00
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),
});
}
}
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
// Partikel der Klangwelten: Regen, Funken, Windschlieren, Glühwürmchen, sonst treibende Lichter
function modeVis(W, H, dpr) {
const M = MODES[S.mode], K = S.modeObj;
if (M.music || !S.running || !K) return;
const a = K.a, add = p => { if (parts.length < 450 ) parts . push ( Object . assign ( { life: 0 , vx: 0 , vy: 0 , h: M . hue } , p ) ) ; } ;
const n = x => Math.random() < x , r = (lo, hi ) = > lo + Math.random() * (hi - lo);
switch (M.vis) {
case "rain":
for (let i = 0; i < 1 + Math . floor ( a * 5 ) ; i + + ) add ( { x: Math . random ( ) * W , y: -8 * dpr , vx: -1 . 2 , vy: r ( 7 , 11 ) , r: r ( 1 . 6 , 3 ) * dpr , max: r ( 40 , 80 ) } ) ;
break;
case "rise":
if (n(.5 + a * .4)) add({ x: W * r(.35, .65), y: H * .9, vx: r(-.5, .5), vy: -r(.8, 2.4), r: r(3, 9) * dpr, max: r(70, 160), h: M.hue + r(-10, 25) });
break;
case "wind":
if (n(.4 + a * .5)) add({ x: -10 * dpr, y: Math.random() * H, vx: r(4, 9) * (.5 + a), vy: r(-.4, .4), r: r(2, 5) * dpr, max: r(60, 140) });
break;
case "fly":
if (n(.05)) add({ x: Math.random() * W, y: H * r(.3, .95), vx: r(-.3, .3), vy: r(-.3, .3), r: r(5, 10) * dpr, max: r(200, 420) });
break;
default:
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) });
}
}
2026-10-04 00:09:28 +02:00
let lastDraw = 0;
2026-10-03 23:08:57 +02:00
function draw(ts) {
rafId = 0;
2026-10-04 00:09:28 +02:00
const W = cv.width, H = cv.height, dpr = DPR();
2026-10-03 23:08:57 +02:00
// 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);
2026-10-04 10:42:45 +02:00
if (LITE & & ts - lastDraw < 48 ) return ; / / Telefone: ~ 20 Bilder / s reichen
2026-10-04 00:09:28 +02:00
lastDraw = ts;
2026-10-03 23:08:57 +02:00
cx.globalCompositeOperation = "source-over";
cx.fillStyle = `rgba(${BG[0]},${BG[1]},${BG[2]},.16)`; cx.fillRect(0, 0, W, H);
if (!S.g) return;
const bins = new Uint8Array(S.g.analyser.frequencyBinCount);
S.g.analyser.getByteFrequencyData(bins);
let lvl = 0; for (let i = 0; i < 80 ; i + + ) lvl + = bins [ i ] ; lvl = lvl / 80 / 255 ;
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
const M0 = MODES[S.mode];
const target = M0.music ? (S.root % 12) * 30 + 200 + S.cur.bright * 40 : M0.hue;
2026-10-03 23:08:57 +02:00
hueV += (((target - hueV + 540) % 360) - 180) * .01;
cx.globalCompositeOperation = "lighter";
// Nebel
for (let i = 0; i < 4 ; i + + ) {
const x = W * (.5 + .38 * Math.sin(ts * .00006 * (i + 1) + i * 2)), y = H * (.5 + .3 * Math.cos(ts * .00005 * (i + 2) + i));
const r = Math.min(W, H) * (.4 + .12 * Math.sin(ts * .0001 + i));
const gr = cx.createRadialGradient(x, y, 0, x, y, r);
gr.addColorStop(0, `hsla(${hueV + i * 28},70%,50%,${.04 + lvl * .16})`); gr.addColorStop(1, "hsla(0,0%,0%,0)");
cx.fillStyle = gr; cx.fillRect(0, 0, W, H);
}
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
modeVis(W, H, dpr);
2026-10-03 23:08:57 +02:00
// Notenereignisse -> Partikel
const now = S.ctx.currentTime;
S.vis = S.vis.filter(ev => { if (ev.t < = now) { if (now - ev.t < 2 ) spawnParticles ( ev ) ; return false ; } return ev . t - now < 120 ; } ) ;
for (let i = parts.length - 1; i >= 0; i--) {
const p = parts[i]; p.life++; p.x += p.vx * dpr + Math.sin((ts + i * 97) * .0007) * .15; p.y += p.vy * dpr;
if (p.life > p.max) { parts.splice(i, 1); continue; }
const k = p.life / p.max, a = Math.sin(Math.PI * k) * .35;
2026-10-04 10:42:45 +02:00
if (LITE) cx.fillStyle = `hsla(${p.h},80%,65%,${a * .45})`; // Telefone: einfache Scheiben statt Verläufen
else {
const gr = cx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.r);
gr.addColorStop(0, `hsla(${p.h},80%,65%,${a})`); gr.addColorStop(1, "hsla(0,0%,0%,0)");
cx.fillStyle = gr;
}
cx.beginPath(); cx.arc(p.x, p.y, p.r, 0, 6.3); cx.fill();
2026-10-03 23:08:57 +02:00
}
Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:09 +02:00
if (S.flash > .02) { cx.fillStyle = `rgba(190,205,255,${S.flash * .28})`; cx.fillRect(0, 0, W, H); S.flash *= .9; }
2026-10-03 23:08:57 +02:00
// Spektrumlinie
cx.lineWidth = 1.6 * dpr; const N = 90;
for (let layer = 0; layer < 2 ; layer + + ) {
cx.beginPath();
for (let i = 0; i < = N; i++) {
const v = bins[Math.floor(Math.pow(i / N, 1.8) * 420) + layer * 3] / 255;
const x = i / N * W, y = H * (.9 - layer * .03) - v * H * .22;
i ? cx.lineTo(x, y) : cx.moveTo(x, y);
}
cx.strokeStyle = `hsla(${hueV + layer * 40},80%,70%,${.4 - layer * .15})`; cx.stroke();
}
}
wake();
post("stopped");
< / script >
< / body >
< / html >