using Godot;
using System;
namespace Chessistics.Scripts.Presentation;
///
/// Procedural sound effects via synthesized waveforms.
/// No external audio files needed — everything is generated in code.
///
public partial class SfxManager : Node
{
private const int SampleRate = 22050;
private const float MasterVolume = 0.15f;
public static SfxManager? Instance { get; private set; }
public override void _Ready()
{
Instance = this;
}
// --- Public API ---
public void PlayPlace() => PlayTone(523.25f, 0.08f, vol: 0.5f); // C5 short blip
public void PlayProduce() => PlayTone(130.81f, 0.12f, vol: 0.3f, wave: Wave.Triangle); // C3 warm
public void PlayTransfer() => PlayNoise(0.12f, vol: 0.15f); // filtered swoosh
public void PlayDeliver() => PlayChord([523.25f, 659.25f], 0.18f, vol: 0.4f); // C5+E5 ding
public void PlayMove() => PlayNoise(0.04f, vol: 0.08f); // tiny whoosh
public void PlayDestroy() => PlaySweep(262f, 65f, 0.18f, vol: 0.4f); // descending crunch
public void PlayVictory() => PlayArpeggio([262f, 330f, 392f, 523f], 0.12f, vol: 0.35f); // C-E-G-C arp
public void PlayClick() => PlayTone(880f, 0.02f, vol: 0.2f); // tiny tick
// --- Synthesis ---
private enum Wave { Sine, Triangle, Square }
private void PlayTone(float freq, float duration, float vol = 0.3f, Wave wave = Wave.Sine)
{
var samples = GenerateTone(freq, duration, vol, wave);
PlaySamples(samples);
}
private void PlayNoise(float duration, float vol = 0.2f)
{
var count = (int)(SampleRate * duration);
var samples = new float[count];
var rng = new Random();
for (int i = 0; i < count; i++)
{
float t = (float)i / count;
float envelope = Envelope(t);
samples[i] = (float)(rng.NextDouble() * 2 - 1) * vol * envelope * MasterVolume;
}
// Simple low-pass: average with previous sample
for (int i = count - 1; i > 0; i--)
samples[i] = (samples[i] + samples[i - 1]) * 0.5f;
PlaySamples(samples);
}
private void PlayChord(float[] freqs, float duration, float vol = 0.3f)
{
var count = (int)(SampleRate * duration);
var samples = new float[count];
foreach (var freq in freqs)
{
var tone = GenerateTone(freq, duration, vol / freqs.Length, Wave.Sine);
for (int i = 0; i < count; i++)
samples[i] += tone[i];
}
PlaySamples(samples);
}
private void PlaySweep(float startFreq, float endFreq, float duration, float vol = 0.3f)
{
var count = (int)(SampleRate * duration);
var samples = new float[count];
for (int i = 0; i < count; i++)
{
float t = (float)i / count;
float freq = Mathf.Lerp(startFreq, endFreq, t);
float envelope = Envelope(t);
float phase = 2f * Mathf.Pi * freq * i / SampleRate;
samples[i] = Mathf.Sin(phase) * vol * envelope * MasterVolume;
}
PlaySamples(samples);
}
private void PlayArpeggio(float[] notes, float noteLength, float vol = 0.3f)
{
float totalDuration = noteLength * notes.Length;
var totalCount = (int)(SampleRate * totalDuration);
var samples = new float[totalCount];
var noteCount = (int)(SampleRate * noteLength);
for (int n = 0; n < notes.Length; n++)
{
int offset = n * noteCount;
var tone = GenerateTone(notes[n], noteLength, vol, Wave.Sine);
for (int i = 0; i < tone.Length && offset + i < totalCount; i++)
samples[offset + i] += tone[i];
}
PlaySamples(samples);
}
private float[] GenerateTone(float freq, float duration, float vol, Wave wave)
{
var count = (int)(SampleRate * duration);
var samples = new float[count];
for (int i = 0; i < count; i++)
{
float t = (float)i / count;
float phase = 2f * Mathf.Pi * freq * i / SampleRate;
float value = wave switch
{
Wave.Triangle => 2f * Mathf.Abs(2f * ((freq * i / SampleRate) % 1f) - 1f) - 1f,
Wave.Square => Mathf.Sin(phase) >= 0 ? 1f : -1f,
_ => Mathf.Sin(phase)
};
float envelope = Envelope(t);
samples[i] = value * vol * envelope * MasterVolume;
}
return samples;
}
/// Simple ADSR-ish envelope: quick attack, sustain, smooth release.
private static float Envelope(float t)
{
if (t < 0.05f) return t / 0.05f; // attack
if (t < 0.3f) return 1f; // sustain
return 1f - (t - 0.3f) / 0.7f; // release
}
private void PlaySamples(float[] samples)
{
var stream = new AudioStreamWav
{
Format = AudioStreamWav.FormatEnum.Format16Bits,
MixRate = SampleRate,
Stereo = false,
Data = FloatsToWav16(samples)
};
var player = new AudioStreamPlayer { Stream = stream, VolumeDb = -6f };
AddChild(player);
player.Finished += () => player.QueueFree();
player.Play();
}
private static byte[] FloatsToWav16(float[] samples)
{
var bytes = new byte[samples.Length * 2];
for (int i = 0; i < samples.Length; i++)
{
short val = (short)(Mathf.Clamp(samples[i], -1f, 1f) * 32767);
bytes[i * 2] = (byte)(val & 0xFF);
bytes[i * 2 + 1] = (byte)((val >> 8) & 0xFF);
}
return bytes;
}
}