DiamondSoundSamples.cs
using System;

namespace Diamonds;

/// <summary>Debris crushing foley, synthesized once as mono PCM.</summary>
public static class DiamondSoundSamples
{
	public const int Rate = 44100;

	public static byte[] Crush()
	{
		var samples = new float[(int)(Rate * 0.16f)];
		var random = new Random( 2609 );
		float[] clicks = [0.012f, 0.029f, 0.054f];
		float low = 0;
		for ( int i = 0; i < samples.Length; i++ )
		{
			float t = (float)i / Rate;
			float noise = (float)random.NextDouble() * 2 - 1;
			low += (noise - low) * 0.24f;
			// Dry granular snap, with several rapidly decaying fracture clicks.
			float envelope = MathF.Exp( -t * 45 );
			foreach ( float start in clicks )
				if ( t >= start ) envelope += 0.38f * MathF.Exp( -(t - start) * 180 );
			float edge = MathF.Min( 1, t / 0.001f ) * MathF.Min( 1, (samples.Length - 1 - i) / (Rate * 0.015f) );
			samples[i] = ((noise - low) * 0.5f + low * 0.7f) * envelope * edge;
		}
		return Pcm( samples, 0.76f );
	}

	/// <summary>A short, deep error buzz for a refused turn.</summary>
	public static byte[] RotateBump()
	{
		const float duration = 0.11f;
		var samples = new float[(int)(Rate * duration)];
		float phase = 0, sub = 0, low = 0;
		for ( int i = 0; i < samples.Length; i++ )
		{
			float t = (float)i / Rate;
			// A saturated low tone sagging from 128 to 92 Hz, over a sine an octave down.
			float frequency = 128 - 36 * (t / duration);
			phase += MathF.Tau * frequency / Rate;
			sub += MathF.Tau * frequency * 0.5f / Rate;
			float buzz = MathF.Tanh( 3 * MathF.Sin( phase ) ) + 0.3f * MathF.Tanh( 3 * MathF.Sin( phase * 1.06f ) );
			// Round off the buzz so it stays dull rather than harsh.
			low += (buzz - low) * 0.16f;
			float envelope = MathF.Min( 1, t / 0.003f ) * MathF.Exp( -t * 20 ) * MathF.Min( 1, (samples.Length - 1 - i) / (Rate * 0.02f) );
			samples[i] = (low * 0.8f + MathF.Sin( sub ) * 0.55f) * envelope;
		}
		return Pcm( samples, 0.8f );
	}

	static byte[] Pcm( float[] samples, float peak )
	{
		float maximum = 0.001f;
		foreach ( float value in samples ) maximum = MathF.Max( maximum, MathF.Abs( value ) );
		var bytes = new byte[samples.Length * 2];
		for ( int i = 0; i < samples.Length; i++ )
		{
			short value = (short)(samples[i] / maximum * peak * short.MaxValue);
			bytes[i * 2] = (byte)(value & 255);
			bytes[i * 2 + 1] = (byte)((value >> 8) & 255);
		}
		return bytes;
	}
}