using Sandbox; using Sandbox.Audio; using System; using System.Collections.Generic; using System.IO; using VeloX.Audio; using static VeloX.EngineStream; namespace VeloX; public class EngineStreamPlayer( EngineStream stream ) : IDisposable { private static readonly Mixer EngineMixer = Mixer.FindMixerByName( "Car Engine" ); public EngineStream Stream { get; set; } = stream; public EngineState EngineState { get; set; } public bool EngineSoundPaused => EngineState != EngineState.Running; public float Throttle { get; set; } public bool IsRedlining { get; set; } public float RPMPercent { get; set; } private float _wobbleTime; public readonly Dictionary EngineSounds = []; public void Update( float deltaTime, Vector3 position, bool isLocal = false ) { var globalPitch = 1.0f; // Gear wobble effect if ( _wobbleTime > 0 ) { _wobbleTime -= deltaTime * (0.1f + Throttle); globalPitch += MathF.Cos( _wobbleTime * Stream.Parameters.WobbleFrequency ) * _wobbleTime * (1 - _wobbleTime) * Stream.Parameters.WobbleStrength; } globalPitch *= Stream.Parameters.Pitch; // Redline effect var redlineVolume = 1.0f; if ( IsRedlining ) { redlineVolume = 1 - Stream.Parameters.RedlineStrength + MathF.Cos( RealTime.Now * Stream.Parameters.RedlineFrequency ) * Stream.Parameters.RedlineStrength; } // Process layers foreach ( var (id, layer) in Stream.Layers ) { EngineSounds.TryGetValue( layer, out var channel ); if ( !channel.IsValid() ) { channel = Sound.PlayFile( layer.AudioPath ); EngineSounds[layer] = channel; } if ( channel.Paused && (EngineSoundPaused || layer.IsMuted) ) continue; // Reset controller outputs float layerVolume = 1.0f; float layerPitch = 1.0f; // Apply all controllers foreach ( var controller in layer.Controllers ) { var inputValue = controller.InputParameter switch { Controller.InputTypes.Throttle => Throttle, Controller.InputTypes.RpmFraction => RPMPercent, _ => 0.0f }; var normalized = Math.Clamp( inputValue, controller.InputRange.Min, controller.InputRange.Max ); var outputValue = controller.InputRange.Remap( normalized, controller.OutputRange.Min, controller.OutputRange.Max ); // Apply to correct parameter switch ( controller.OutputParameter ) { case Controller.OutputTypes.Volume: layerVolume *= outputValue; break; case Controller.OutputTypes.Pitch: layerPitch *= outputValue; break; } } // Apply redline effect if needed layerVolume *= layer.UseRedline ? redlineVolume : 1.0f; layerPitch *= globalPitch; // Update audio channel channel.Pitch = layerPitch; channel.Volume = layerVolume * Stream.Parameters.Volume; channel.Position = position; channel.ListenLocal = isLocal; channel.Paused = EngineSoundPaused || layer.IsMuted; channel.TargetMixer = EngineMixer; } } public void Dispose() { foreach ( var item in EngineSounds ) { item.Value?.Stop(); item.Value?.Dispose(); } } }