288 lines
11 KiB
C#
288 lines
11 KiB
C#
using UnityEngine;
|
||
|
||
/// <summary>
|
||
/// Процедурный генератор звуковых эффектов и атмосферы для вступительной кат-сцены «Синтезия».
|
||
/// Все звуки генерируются программно в памяти (AudioClip.Create) и не требуют внешних файлов.
|
||
/// </summary>
|
||
public static class IntroCutsceneAudio
|
||
{
|
||
private static int sampleRate = 44100;
|
||
|
||
/// <summary>
|
||
/// Низкий давящий гул заброшенного подземного бункера (зацикленный)
|
||
/// </summary>
|
||
public static AudioClip CreateBunkerHumClip(float duration = 6f)
|
||
{
|
||
int samples = (int)(sampleRate * duration);
|
||
float[] data = new float[samples];
|
||
System.Random rnd = new System.Random(1337);
|
||
|
||
float lastNoise = 0f;
|
||
for (int i = 0; i < samples; i++)
|
||
{
|
||
float t = (float)i / sampleRate;
|
||
// Низкочастотный рокот бункера: 45 Гц + 68 Гц с биениями
|
||
float sub = Mathf.Sin(2f * Mathf.PI * 45f * t) * 0.45f;
|
||
float drone = Mathf.Sin(2f * Mathf.PI * 68f * t + Mathf.Sin(2f * Mathf.PI * 0.4f * t)) * 0.25f;
|
||
|
||
// Фильтрованный низкочастотный шум воздуха в вентиляции
|
||
float white = ((float)rnd.NextDouble() * 2f - 1f) * 0.15f;
|
||
lastNoise = Mathf.Lerp(lastNoise, white, 0.04f);
|
||
|
||
// Бесшовное зацикливание по краям
|
||
float edgeEnv = 1f;
|
||
float edgeTime = 0.5f;
|
||
if (t < edgeTime) edgeEnv = t / edgeTime;
|
||
else if (t > duration - edgeTime) edgeEnv = (duration - t) / edgeTime;
|
||
|
||
data[i] = (sub + drone + lastNoise) * edgeEnv * 0.7f;
|
||
}
|
||
|
||
AudioClip clip = AudioClip.Create("BunkerHum", samples, 1, sampleRate, false);
|
||
clip.SetData(data, 0);
|
||
return clip;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Глухой ритмичный стук сердца (двойной удар тук-тук)
|
||
/// </summary>
|
||
public static AudioClip CreateHeartbeatClip(float bpm = 65f)
|
||
{
|
||
float period = 60f / bpm;
|
||
int samples = (int)(sampleRate * period);
|
||
float[] data = new float[samples];
|
||
|
||
for (int i = 0; i < samples; i++)
|
||
{
|
||
float t = (float)i / sampleRate;
|
||
float val = 0f;
|
||
|
||
// Первый удар (Систола, 0.0с - 0.18с)
|
||
if (t < 0.20f)
|
||
{
|
||
float localT = t / 0.20f;
|
||
float env = Mathf.Sin(localT * Mathf.PI) * Mathf.Exp(-localT * 3.5f);
|
||
float freq = Mathf.Lerp(65f, 38f, localT);
|
||
val += Mathf.Sin(2f * Mathf.PI * freq * t) * env * 0.85f;
|
||
}
|
||
|
||
// Второй удар (Диастола, 0.24с - 0.44с)
|
||
float t2 = t - 0.24f;
|
||
if (t2 >= 0f && t2 < 0.20f)
|
||
{
|
||
float localT = t2 / 0.20f;
|
||
float env = Mathf.Sin(localT * Mathf.PI) * Mathf.Exp(-localT * 4.0f);
|
||
float freq = Mathf.Lerp(55f, 34f, localT);
|
||
val += Mathf.Sin(2f * Mathf.PI * freq * t2) * env * 0.65f;
|
||
}
|
||
|
||
data[i] = Mathf.Clamp(val, -1f, 1f);
|
||
}
|
||
|
||
AudioClip clip = AudioClip.Create("Heartbeat", samples, 1, sampleRate, false);
|
||
clip.SetData(data, 0);
|
||
return clip;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Пиканье кардиомонитора (медицинский зуммер)
|
||
/// </summary>
|
||
public static AudioClip CreateHeartMonitorBeepClip()
|
||
{
|
||
float duration = 0.14f;
|
||
int samples = (int)(sampleRate * duration);
|
||
float[] data = new float[samples];
|
||
|
||
for (int i = 0; i < samples; i++)
|
||
{
|
||
float t = (float)i / samples;
|
||
float env = Mathf.Sin(t * Mathf.PI);
|
||
// Чистый чирп частоты 940 Гц
|
||
float tone = Mathf.Sin(2f * Mathf.PI * 940f * ((float)i / sampleRate));
|
||
data[i] = tone * env * 0.35f;
|
||
}
|
||
|
||
AudioClip clip = AudioClip.Create("HeartMonitorBeep", samples, 1, sampleRate, false);
|
||
clip.SetData(data, 0);
|
||
return clip;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Скрежет ногтей по стене и искаженный стон безумия
|
||
/// </summary>
|
||
public static AudioClip CreateScratchAndGroanClip()
|
||
{
|
||
float duration = 3.2f;
|
||
int samples = (int)(sampleRate * duration);
|
||
float[] data = new float[samples];
|
||
System.Random rnd = new System.Random(42);
|
||
|
||
float lastVal = 0f;
|
||
for (int i = 0; i < samples; i++)
|
||
{
|
||
float t = (float)i / sampleRate;
|
||
|
||
// Скрежет ногтей по бетону (высокочастотный фрикционный скрежет)
|
||
float scratchNoise = ((float)rnd.NextDouble() * 2f - 1f);
|
||
float scratchFilt = scratchNoise - lastVal;
|
||
lastVal = scratchNoise * 0.45f;
|
||
float scratchMod = Mathf.Sin(2f * Mathf.PI * 7.5f * t) * 0.5f + 0.5f;
|
||
float scratch = scratchFilt * scratchMod * 0.22f;
|
||
|
||
// Заглушенный утробный стон
|
||
float groanEnv = Mathf.Sin(Mathf.Clamp01(t / 2.8f) * Mathf.PI);
|
||
float groanFreq = Mathf.Lerp(140f, 90f, t / 3.2f) + Mathf.Sin(2f * Mathf.PI * 5f * t) * 12f;
|
||
float groan = Mathf.Sin(2f * Mathf.PI * groanFreq * t) * groanEnv * 0.35f;
|
||
|
||
data[i] = (scratch + groan) * 0.75f;
|
||
}
|
||
|
||
AudioClip clip = AudioClip.Create("ScratchGroan", samples, 1, sampleRate, false);
|
||
clip.SetData(data, 0);
|
||
return clip;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Скрип сжатого в руке металла
|
||
/// </summary>
|
||
public static AudioClip CreateMetalGripClip()
|
||
{
|
||
float duration = 1.4f;
|
||
int samples = (int)(sampleRate * duration);
|
||
float[] data = new float[samples];
|
||
|
||
for (int i = 0; i < samples; i++)
|
||
{
|
||
float t = (float)i / samples;
|
||
float env = Mathf.Sin(t * Mathf.PI) * Mathf.Exp(-t * 2.2f);
|
||
// Высокий металлический скрип напряжения
|
||
float creak = Mathf.Sin(2f * Mathf.PI * (1450f + Mathf.Sin(t * 40f) * 350f) * ((float)i / sampleRate));
|
||
float lowClank = Mathf.Sin(2f * Mathf.PI * 220f * ((float)i / sampleRate)) * Mathf.Exp(-t * 8f);
|
||
data[i] = (creak * 0.25f + lowClank * 0.45f) * env;
|
||
}
|
||
|
||
AudioClip clip = AudioClip.Create("MetalGrip", samples, 1, sampleRate, false);
|
||
clip.SetData(data, 0);
|
||
return clip;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Гортанный хищный рык мутанта вдалеке
|
||
/// </summary>
|
||
public static AudioClip CreateDistantGrowlClip()
|
||
{
|
||
float duration = 2.4f;
|
||
int samples = (int)(sampleRate * duration);
|
||
float[] data = new float[samples];
|
||
System.Random rnd = new System.Random(999);
|
||
|
||
for (int i = 0; i < samples; i++)
|
||
{
|
||
float t = (float)i / sampleRate;
|
||
float env = Mathf.Sin(Mathf.Clamp01(t / 2.4f) * Mathf.PI);
|
||
|
||
// Вибрирующий гортанный рык
|
||
float am = Mathf.Sin(2f * Mathf.PI * 28f * t) * 0.5f + 0.5f;
|
||
float freq = Mathf.Lerp(110f, 65f, t / 2.4f);
|
||
float tone = Mathf.Sin(2f * Mathf.PI * freq * t) * am;
|
||
float noise = ((float)rnd.NextDouble() * 2f - 1f) * 0.35f * am;
|
||
|
||
data[i] = (tone * 0.6f + noise * 0.4f) * env * 0.65f;
|
||
}
|
||
|
||
AudioClip clip = AudioClip.Create("DistantGrowl", samples, 1, sampleRate, false);
|
||
clip.SetData(data, 0);
|
||
return clip;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Оглушительный удар металла о стену
|
||
/// </summary>
|
||
public static AudioClip CreateMetalClangClip()
|
||
{
|
||
float duration = 1.8f;
|
||
int samples = (int)(sampleRate * duration);
|
||
float[] data = new float[samples];
|
||
System.Random rnd = new System.Random(555);
|
||
|
||
for (int i = 0; i < samples; i++)
|
||
{
|
||
float t = (float)i / samples;
|
||
// Ударный импульс
|
||
float impEnv = Mathf.Exp(-t * 22f);
|
||
float ringEnv = Mathf.Exp(-t * 3.5f);
|
||
|
||
float noise = ((float)rnd.NextDouble() * 2f - 1f) * impEnv * 0.8f;
|
||
// Металлические гармоники гаечного ключа (звон 440, 880, 1320, 2400 Гц)
|
||
float tone1 = Mathf.Sin(2f * Mathf.PI * 520f * ((float)i / sampleRate));
|
||
float tone2 = Mathf.Sin(2f * Mathf.PI * 1150f * ((float)i / sampleRate));
|
||
float tone3 = Mathf.Sin(2f * Mathf.PI * 2350f * ((float)i / sampleRate));
|
||
|
||
float harmonics = (tone1 * 0.45f + tone2 * 0.35f + tone3 * 0.2f) * ringEnv;
|
||
data[i] = (noise + harmonics) * 0.9f;
|
||
}
|
||
|
||
AudioClip clip = AudioClip.Create("MetalClang", samples, 1, sampleRate, false);
|
||
clip.SetData(data, 0);
|
||
return clip;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Яростный визг мутанта при броске
|
||
/// </summary>
|
||
public static AudioClip CreateFuriousScreechClip()
|
||
{
|
||
float duration = 1.6f;
|
||
int samples = (int)(sampleRate * duration);
|
||
float[] data = new float[samples];
|
||
System.Random rnd = new System.Random(777);
|
||
|
||
for (int i = 0; i < samples; i++)
|
||
{
|
||
float t = (float)i / samples;
|
||
float env = Mathf.Sin(t * Mathf.PI) * Mathf.Exp(-t * 1.8f);
|
||
|
||
// Резкий частотный спад от 1600 Гц до 450 Гц с жестким дисторшном
|
||
float freq = Mathf.Lerp(1600f, 450f, t);
|
||
float tone = Mathf.Sin(2f * Mathf.PI * freq * ((float)i / sampleRate));
|
||
float noise = ((float)rnd.NextDouble() * 2f - 1f) * 0.4f;
|
||
|
||
float raw = (tone + noise) * env * 1.5f;
|
||
data[i] = Mathf.Clamp(raw, -0.9f, 0.9f); // Сатурация
|
||
}
|
||
|
||
AudioClip clip = AudioClip.Create("FuriousScreech", samples, 1, sampleRate, false);
|
||
clip.SetData(data, 0);
|
||
return clip;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Низкий басовый кинематографичный удар (Cinematic Boom) под появление логотипа SYNTHESIA
|
||
/// </summary>
|
||
public static AudioClip CreateCinematicBoomClip()
|
||
{
|
||
float duration = 3.5f;
|
||
int samples = (int)(sampleRate * duration);
|
||
float[] data = new float[samples];
|
||
|
||
for (int i = 0; i < samples; i++)
|
||
{
|
||
float t = (float)i / samples;
|
||
// Ударный спад
|
||
float subEnv = Mathf.Exp(-t * 1.6f);
|
||
// Скольжение частоты саб-баса: 110 Гц -> 36 Гц
|
||
float freq = Mathf.Lerp(110f, 36f, Mathf.Pow(t, 0.6f));
|
||
float sub = Mathf.Sin(2f * Mathf.PI * freq * ((float)i / sampleRate));
|
||
|
||
// Теплый обертон
|
||
float harmonic = Mathf.Sin(2f * Mathf.PI * (freq * 2.05f) * ((float)i / sampleRate)) * 0.35f;
|
||
|
||
data[i] = (sub + harmonic) * subEnv * 0.95f;
|
||
}
|
||
|
||
AudioClip clip = AudioClip.Create("CinematicBoom", samples, 1, sampleRate, false);
|
||
clip.SetData(data, 0);
|
||
return clip;
|
||
}
|
||
}
|