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;
}
}