Files
Car-Simulation/Car simulation/EngineSound.cs
2025-12-18 03:00:13 +01:00

147 lines
4.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using SFML.Audio;
using SFML.System;
using System;
namespace Car_simulation
{
public class EngineSound : SoundStream
{
// Audio properties - smaller buffer for less latency
private const uint SAMPLE_RATE = 44100;
private const ushort CHANNEL_COUNT = 2; // Stereo
private const float BUFFER_DURATION = 0.05f; // 10ms instead of 50ms!
// Engine sound properties - NO SMOOTHING for instant response
private volatile float _currentRPM = 800f; // volatile for thread safety
private volatile float _currentThrottle = 0f;
private float _volume = 0.3f;
private bool _isPlaying = false;
// Harmonic series - DIRECT RPM TO FREQUENCY
private float[] _harmonicRatios = { 1f, 2f, 4f, 6f };
private float[] _harmonicAmplitudes = { 1f, 0.3f, 0.1f, 0.05f };
private float[] _harmonicPhases = new float[4];
// Engine configuration - for direct RPM calculation
public int CylinderCount { get; set; } = 4;
public float FiringFrequencyMultiplier => CylinderCount / 2f; // 4-stroke engines
// For RPM to frequency mapping
private float _rpmToHzFactor;
private Random _random = new Random();
public EngineSound()
{
Initialize(CHANNEL_COUNT, SAMPLE_RATE);
// Calculate direct conversion factor
// RPM to Hz: (RPM / 60) × (Cylinders / 2) for 4-stroke
_rpmToHzFactor = (1f / 60f) * (CylinderCount / 2f);
// Initialize phases
for (int i = 0; i < _harmonicPhases.Length; i++)
{
_harmonicPhases[i] = (float)(_random.NextDouble() * 2 * Math.PI);
}
}
// CALL THIS FROM YOUR PHYSICS THREAD - INSTANT UPDATE
public void SetEngineState(float rpm, float throttle)
{
// NO LOCK, NO SMOOTHING - DIRECT ASSIGNMENT
_currentRPM = rpm;
_currentThrottle = throttle;
// Volume based on throttle (instant)
_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)
{
// SMALLER BUFFER: 10ms instead of 50ms
int sampleCount = (int)(SAMPLE_RATE * BUFFER_DURATION) * 2; // *2 for stereo
samples = new short[sampleCount];
// Get current values ONCE per buffer (not per sample)
float rpm = _currentRPM;
float throttle = _currentThrottle;
float volume = _volume;
// DIRECT RPM TO FREQUENCY - NO SMOOTHING
float baseFrequency = rpm * _rpmToHzFactor; // (RPM/60) × (cylinders/2)
// Pre-calculate harmonic frequencies
float[] harmonicFrequencies = new float[_harmonicRatios.Length];
float[] phaseIncrements = new float[_harmonicRatios.Length];
for (int h = 0; h < _harmonicRatios.Length; h++)
{
harmonicFrequencies[h] = baseFrequency * _harmonicRatios[h];
phaseIncrements[h] = harmonicFrequencies[h] * 2f * MathF.PI / SAMPLE_RATE;
}
// Calculate roughness factor
float roughness = 0.02f * throttle;
// Generate sound
for (int i = 0; i < sampleCount; i += 2)
{
float sampleValue = 0f;
// Sum all harmonics
for (int h = 0; h < _harmonicRatios.Length; h++)
{
sampleValue += MathF.Sin(_harmonicPhases[h]) * _harmonicAmplitudes[h];
_harmonicPhases[h] += phaseIncrements[h];
if (_harmonicPhases[h] > 2f * MathF.PI)
_harmonicPhases[h] -= 2f * MathF.PI;
}
// Add roughness
sampleValue += (float)(_random.NextDouble() * 2 - 1) * roughness;
// Apply volume
sampleValue *= volume;
// Clamp and convert
sampleValue = Math.Clamp(sampleValue, -1f, 1f);
short sample = (short)(sampleValue * 32767);
// Stereo
samples[i] = sample;
samples[i + 1] = sample;
}
return true;
}
protected override void OnSeek(Time timeOffset)
{
// Reset phases
for (int i = 0; i < _harmonicPhases.Length; i++)
{
_harmonicPhases[i] = (float)(_random.NextDouble() * 2 * Math.PI);
}
}
}
}