diff --git a/apps/game/assets/audio/ambience/meadows.wav b/apps/game/assets/audio/ambience/meadows.wav index 56524d8..ea119cd 100644 --- a/apps/game/assets/audio/ambience/meadows.wav +++ b/apps/game/assets/audio/ambience/meadows.wav Binary files differ diff --git a/apps/game/assets/audio/ambience/meadows_night.wav b/apps/game/assets/audio/ambience/meadows_night.wav index a8254ab..451606c 100644 --- a/apps/game/assets/audio/ambience/meadows_night.wav +++ b/apps/game/assets/audio/ambience/meadows_night.wav Binary files differ diff --git a/apps/game/tools/audio/gen.mjs b/apps/game/tools/audio/gen.mjs index 598b36b..23dc1f8 100644 --- a/apps/game/tools/audio/gen.mjs +++ b/apps/game/tools/audio/gen.mjs @@ -176,30 +176,83 @@ } /** Амбиент лугов: настоящий ветер — воздушная полоса, порывы шумом, зациклен. */ -function ambienceMeadows() { - const dur = 8; - const air = bandNoise(62, 250, 1800, dur); - const whistle = bandNoise(63, 2000, 4000, dur); - const r = makeRng(7); +/** + * Фактура ветра лугов: мягкий низкий шум (120–600 Гц) с «дыханием» — суммой + * медленных волн — и порывами. Возвращает замыкание t → вклад ветра. + */ +function meadowsWind(seed, dur, { gustCount, gustAmp, breathAmp }) { + const air = bandNoise(seed, 120, 600, dur); + const r = makeRng(seed * 31 + 5); const gusts = []; - for (let i = 0; i < 4; i++) gusts.push({ at: r() * dur, len: 0.6 + r() * 1.2 }); - const s = synth(dur, (t) => { - // «Дыхание» ветра: сумма нескольких медленных волн — без ровного прилива. + for (let i = 0; i < gustCount; i++) { + gusts.push({ at: r() * dur, len: 0.8 + r() * 1.6, amp: (0.5 + r() * 0.5) * gustAmp }); + } + return (t) => { const breath = - 0.55 + - 0.22 * Math.sin((2 * Math.PI * t) / dur + 1) + + 0.5 + + 0.28 * Math.sin((2 * Math.PI * t) / dur + 1) + 0.14 * Math.sin((4 * Math.PI * t) / dur + 3.1) + - 0.09 * Math.sin((10 * Math.PI * t) / dur + 5.2); - let v = air() * 0.22 * breath; + 0.08 * Math.sin((9 * Math.PI * t) / dur + 5.2); + let v = air() * breathAmp * breath; for (const g of gusts) { const d = t - g.at + (t < g.at ? dur : 0); // зациклить порывы - if (d >= 0 && d < g.len) v += air() * 0.16 * decay(d, g.len, 1.5); + if (d >= 0 && d < g.len) v += air() * g.amp * decay(d, g.len, 1.4); } - // Тихий высокий свист лишь на пике дыхания. - v += whistle() * 0.05 * Math.max(0, breath - 0.55) * 2; + return v; + }; +} + +/** Шуршание сухой травы: короткие всплески узкой полосы 1.5–3 кГц. */ +function grassRustle(seed, dur, count, amp) { + const rustle = bandNoise(seed, 1500, 3000, dur); + const r = makeRng(seed * 17 + 3); + const bursts = []; + for (let i = 0; i < count; i++) bursts.push({ at: r() * dur, len: 0.1 + r() * 0.25, amp: 0.5 + r() * 0.5 }); + return (t) => { + let v = 0; + for (const b of bursts) { + const d = t - b.at + (t < b.at ? dur : 0); + if (d >= 0 && d < b.len) v += rustle() * amp * b.amp * decay(d, b.len, 2.5); + } + return v; + }; +} + +/** Скрип дерева/столба: низкий тон с изгибом частоты, короткий. */ +function woodCreak(at, len, f0, amp) { + return (t) => { + const d = t - at; + if (d < 0 || d >= len) return 0; + // Изгиб частоты: два медленных колебания поверх базовой — «скрип-скрип». + const f = f0 + 0.35 * f0 * Math.sin((2 * Math.PI * d) / 0.22); + return Math.sin(2 * Math.PI * f * d) * amp * decay(d, len, 2.2); + }; +} + +/** + * Амбиент лугов: тихий «дыхательный» ветер (мягкий низкий шум с порывами), + * шуршание сухой травы и редкие события — скрипы деревьев, далёкий звон + * Звенца. События распределены неравномерно, луп не «пульсирует». + */ +function ambienceMeadows() { + const dur = 8; + const wind = meadowsWind(62, dur, { gustCount: 5, gustAmp: 0.22, breathAmp: 0.3 }); + const rustle = grassRustle(63, dur, 7, 0.12); + const creaks = [ + woodCreak(0.8, 0.5, 140, 0.1), + woodCreak(3.4, 0.7, 95, 0.12), + woodCreak(6.1, 0.4, 180, 0.08) + ]; + const s = synth(dur, (t) => { + let v = wind(t) + rustle(t); + for (const creak of creaks) v += creak(t); + // Далёкий звон Звенца (111 Гц), один за цикл, глухо — сквозь ветер. + const db = t - 5.3; + if (db >= 0 && db < 2) + v += (Math.sin(2 * Math.PI * 111 * db) * 0.05 + Math.sin(2 * Math.PI * 111 * 2.76 * db) * 0.015) * decay(db, 2, 3); return v; }); - return loopify(normalize(s, 0.5)); + return loopify(normalize(s, 0.32)); } /** Амбиент прудов: ниже, влажнее, с редкой капелью. */ @@ -254,31 +307,23 @@ */ function ambienceMeadowsNight() { const dur = 8; - const air = bandNoise(72, 80, 700, dur); - const hum = bandNoise(73, 40, 160, dur); - const r = makeRng(17); - const gusts = []; - for (let i = 0; i < 3; i++) gusts.push({ at: r() * dur, len: 1 + r() * 1.5 }); - const bells = [2.5, 6.5]; + // Та же фактура ветра, что днём, но глубже, тише и с меньшими порывами. + const wind = meadowsWind(72, dur, { gustCount: 3, gustAmp: 0.12, breathAmp: 0.32 }); + const rustle = grassRustle(73, dur, 3, 0.06); + const hum = bandNoise(74, 40, 160, dur); + const creaks = [woodCreak(2.2, 0.6, 110, 0.07), woodCreak(6.8, 0.5, 90, 0.05)]; const s = synth(dur, (t) => { - // Ночное «дыхание» медленнее и глубже дневного. - const breath = 0.5 + 0.3 * Math.sin((2 * Math.PI * t) / dur + 2) + 0.2 * Math.sin((6 * Math.PI * t) / dur + 4); - let v = air() * 0.3 * breath; - for (const g of gusts) { - const d = t - g.at + (t < g.at ? dur : 0); - if (d >= 0 && d < g.len) v += air() * 0.2 * decay(d, g.len, 1.5); - } + let v = wind(t) + rustle(t); + for (const creak of creaks) v += creak(t); // Дальний гул машины: 55 Гц с медленным тремоло, как будто земля дышит. - v += (Math.sin(2 * Math.PI * 55 * t) * 0.1 + hum() * 0.12) * (0.7 + 0.3 * Math.sin(2 * Math.PI * t / 3)); + v += (Math.sin(2 * Math.PI * 55 * t) * 0.08 + hum() * 0.1) * (0.7 + 0.3 * Math.sin(2 * Math.PI * t / 3)); // Далёкий колокол: глухой удар, приходящий сквозь ветер. - for (const at of bells) { - const dt = t - at + (t < at ? dur : 0); - if (dt >= 0 && dt < 2) - v += (Math.sin(2 * Math.PI * 111 * dt) * 0.12 + Math.sin(2 * Math.PI * 111 * 2.76 * dt) * 0.04) * decay(dt, 2, 3); - } + const dt = t - 4.6; + if (dt >= 0 && dt < 2) + v += (Math.sin(2 * Math.PI * 111 * dt) * 0.08 + Math.sin(2 * Math.PI * 111 * 2.76 * dt) * 0.025) * decay(dt, 2, 3); return v; }); - return loopify(normalize(s, 0.5)); + return loopify(normalize(s, 0.3)); } /** Ночь у прудов: ветер почти стих, вода слышна громче, капли чаще. */