/// <summary> /// If exist, immediately stop and destroy all effects with given parameters. /// </summary> public static TR_AUDIO_SEND Kill(int effectID, TR_AUDIO_EMITTER emitterType = TR_AUDIO_EMITTER.Global, int emitterID = 0) { var playingSound = IsEffectPlaying(effectID, emitterType, emitterID); if(playingSound != -1) { EngineWorld.AudioSources[playingSound].Stop(); return TR_AUDIO_SEND.Processed; } return TR_AUDIO_SEND.Ignored; }
public static int IsEffectPlaying(int effectID = -1, TR_AUDIO_EMITTER emitterType = TR_AUDIO_EMITTER.Unknown, int emitterID = -1) { for (var i = 0; i < EngineWorld.AudioSources.Count; i++) { var c = EngineWorld.AudioSources[i]; if((effectID == -1 || effectID == c.EffectIndex) && (emitterType == TR_AUDIO_EMITTER.Unknown || emitterType == c.EmitterType) && (emitterID == -1 || emitterID == c.EmitterID)) { if (c.IsPlaying) return i; } } return -1; }
/// <summary> /// Send to play effect with given parameters. /// </summary> public static TR_AUDIO_SEND Send(uint effectID, TR_AUDIO_EMITTER emitterType = TR_AUDIO_EMITTER.Global, int emitterID = 0) { var sourceNumber = 0; AudioEffect effect; // If there are no audio buffers or effect index is wrong, don't process. if (EngineWorld.AudioBuffers.Length == 0 || effectID < 0) return TR_AUDIO_SEND.Ignored; // Remap global engine effect ID to local effect ID. if (effectID >= EngineWorld.AudioMap.Count) { return TR_AUDIO_SEND.NoSample; // Sound is out of bounds; stop. } var realID = (int) EngineWorld.AudioMap[(int) effectID]; // Pre-step 1: if there is no effect associated with this ID, bypass audio send. if (realID == -1) { return TR_AUDIO_SEND.Ignored; } else { effect = EngineWorld.AudioEffects[realID]; } // Pre-step 2: check if sound non-looped and chance to play isn't zero, // then randomly select if it should be played or not. if (effect.Loop != LoopType.Forward && effect.Chance > 0) { if (effect.Chance < Helper.CPPRand() % 0x7FFF) { // Bypass audio send, if chance test is not passed. return TR_AUDIO_SEND.Ignored; } } // Pre-step 3: Calculate if effect's hearing sphere intersect listener's hearing sphere. // If it's not, bypass audio send (cause we don't want it to occupy channel, if it's not // heard). if (!IsInRange(emitterType, emitterID, effect.Range, effect.Gain)) { return TR_AUDIO_SEND.Ignored; } // Pre-step 4: check if R (Rewind) flag is set for this effect, if so, // find any effect with similar ID playing for this entity, and stop it. // Otherwise, if W (Wait) or L (Looped) flag is set, and same effect is // playing for current entity, don't send it and exit function. sourceNumber = IsEffectPlaying((int) effectID, emitterType, emitterID); if (sourceNumber != -1) { if (effect.Loop == LoopType.PingPong) { EngineWorld.AudioSources[sourceNumber].Stop(); } else if (effect.Loop != LoopType.None) // Any other looping case (Wait / Loop). { return TR_AUDIO_SEND.Ignored; } } else { sourceNumber = GetFreeSource(); // Get free source } if (sourceNumber != -1) // Everything is OK, we're sending audio to channel. { var bufferIndex = 0; // Step 1. Assign buffer to source. if (effect.SampleCount > 1) { // Select random buffer, if effect info contains more than 1 assigned samples. bufferIndex = unchecked((int) (Helper.CPPRand() % effect.SampleCount + effect.SampleIndex)); } else { // Just assign buffer to source, if there is only one assigned sample. bufferIndex = (int) effect.SampleIndex; } var source = EngineWorld.AudioSources[sourceNumber]; source.SetBuffer(bufferIndex); // Step 2. Check looped flag, and if so, set source type to looped. source.IsLooping = effect.Loop == LoopType.Forward; // Step 3. Apply internal sound parameters. source.EmitterID = emitterID; source.EmitterType = emitterType; source.EffectIndex = effectID; // Step 4. Apply sound effect properties. source.Pitch = effect.Pitch; if (effect.RandomizePitch) // Vary pitch, if flag is set. { source.Pitch += (Helper.CPPRand() % effect.RandomizePitchVar - 25.0f) / 200.0f; } source.Gain = effect.Gain; if (effect.RandomizeGain) // Vary gain, if flag is set. { source.Gain += (Helper.CPPRand() % effect.RandomizeGainVar - 25.0f) / 200.0f; } source.SetRange(effect.Range); // Set audible range source.Play(); // Everything is OK, play sound now! return TR_AUDIO_SEND.Processed; } return TR_AUDIO_SEND.NoChannel; }
public static bool IsInRange(TR_AUDIO_EMITTER emitterType, int entityID, float range, float gain) { var vec = Vector3.Zero; switch (emitterType) { case TR_AUDIO_EMITTER.Entity: var ent = EngineWorld.GetEntityByID((uint)entityID); if (ent == null) return false; vec = ent.Transform.Origin; break; case TR_AUDIO_EMITTER.SoundSource: if ((uint) entityID + 1 > EngineWorld.AudioEmitters.Count) return false; vec = EngineWorld.AudioEmitters[entityID].Position; break; case TR_AUDIO_EMITTER.Global: return true; default: return false; } // We add 1/4 of overall distance to fix up some issues with // pseudo-looped sounds that are called at certain frames in animations. var dist = (ListenerPosition - vec).LengthSquared / (gain + 1.25f); return dist < range * range; }