149 lines
4.9 KiB
C#
149 lines
4.9 KiB
C#
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.01f; // 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);
|
||
}
|
||
|
||
Console.WriteLine($"EngineSound initialized: {BUFFER_DURATION * 1000:F0}ms buffer, {CylinderCount} cylinders");
|
||
}
|
||
|
||
// 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);
|
||
}
|
||
}
|
||
}
|
||
} |