111 lines
3.4 KiB
C#
111 lines
3.4 KiB
C#
using SFML.Audio;
|
|
using SFML.System;
|
|
using System;
|
|
|
|
namespace Car_simulation
|
|
{
|
|
public class EngineSound : SoundStream
|
|
{
|
|
private const uint SAMPLE_RATE = 44100;
|
|
private const ushort CHANNELS = 2;
|
|
private const float BUFFER_DURATION = 0.01f;
|
|
|
|
private volatile float _currentRPM = 800f;
|
|
private volatile float _currentThrottle = 0f;
|
|
private float _volume = 0.3f;
|
|
private bool _isPlaying = false;
|
|
|
|
private readonly int _cylinders = 4;
|
|
private readonly Random _rand = new Random();
|
|
|
|
// Engine harmonics
|
|
private readonly float[] _harmonicMultiples = { 1f, 2f, 3f, 4f };
|
|
private readonly float[] _harmonicAmps = { 0.3f, 0.6f, 0.4f, 0.2f };
|
|
private readonly float[] _harmonicPhases = new float[4];
|
|
|
|
public EngineSound()
|
|
{
|
|
Initialize(CHANNELS, SAMPLE_RATE);
|
|
for (int i = 0; i < _harmonicPhases.Length; i++)
|
|
_harmonicPhases[i] = (float)(_rand.NextDouble() * 2 * Math.PI);
|
|
}
|
|
|
|
public void SetEngineState(float rpm, float throttle)
|
|
{
|
|
_currentRPM = rpm;
|
|
_currentThrottle = throttle;
|
|
_volume = 0.1f + 0.4f * throttle;
|
|
}
|
|
|
|
public void StartSound()
|
|
{
|
|
if (!_isPlaying)
|
|
{
|
|
Play();
|
|
_isPlaying = true;
|
|
}
|
|
}
|
|
|
|
public void StopSound()
|
|
{
|
|
if (_isPlaying)
|
|
{
|
|
Stop();
|
|
_isPlaying = false;
|
|
}
|
|
}
|
|
|
|
protected override bool OnGetData(out short[] samples)
|
|
{
|
|
int sampleCount = (int)(SAMPLE_RATE * BUFFER_DURATION) * CHANNELS;
|
|
samples = new short[sampleCount];
|
|
|
|
for (int i = 0; i < sampleCount; i += 2)
|
|
{
|
|
float sampleValue = 0f;
|
|
|
|
// Read RPM per sample (instant)
|
|
float rpm = _currentRPM;
|
|
float throttle = _currentThrottle;
|
|
|
|
// Base frequency for 4-cylinder, 4-stroke
|
|
float baseFreq = (rpm / 60f) * (_cylinders / 2f);
|
|
|
|
// Sum harmonics
|
|
for (int h = 0; h < _harmonicMultiples.Length; h++)
|
|
{
|
|
float freq = baseFreq * _harmonicMultiples[h];
|
|
float phaseInc = freq * 2f * MathF.PI / SAMPLE_RATE;
|
|
|
|
// Slight jitter for realism
|
|
float jitter = 0.001f * ((float)_rand.NextDouble() - 0.5f);
|
|
_harmonicPhases[h] += phaseInc + jitter;
|
|
|
|
if (_harmonicPhases[h] > 2f * MathF.PI)
|
|
_harmonicPhases[h] -= 2f * MathF.PI;
|
|
|
|
sampleValue += MathF.Sin(_harmonicPhases[h]) * _harmonicAmps[h];
|
|
}
|
|
|
|
// Add noise for roughness
|
|
sampleValue += ((float)_rand.NextDouble() * 2f - 1f) * 0.02f * throttle;
|
|
|
|
// Volume and clamp
|
|
sampleValue = Math.Clamp(sampleValue * _volume, -1f, 1f);
|
|
short finalSample = (short)(sampleValue * 32767);
|
|
|
|
samples[i] = finalSample;
|
|
samples[i + 1] = finalSample;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
protected override void OnSeek(Time timeOffset)
|
|
{
|
|
for (int i = 0; i < _harmonicPhases.Length; i++)
|
|
_harmonicPhases[i] = (float)(_rand.NextDouble() * 2 * Math.PI);
|
|
}
|
|
}
|
|
}
|