// MotionReactiveSound.cs using Simuspaces.Simulation; using System.Collections; public class MotionReactiveSound : SimuScriptBase { protected override bool SyncTransformStateByDefault => false; public SceneObject AudioObject; public int AudioSourceIndex = 0; public bool IncludeAudioChildren = true; public float SampleInterval = 0.08f; public float MovementThreshold = 0.005f; public bool PreferRigidbodyVelocity = true; public float VelocitySmoothing = 7f; public float MinSpeedToPlay = 0.12f; public float MaxSpeedForPitch = 16f; public float MinPitch = 0.65f; public float MaxPitch = 1.65f; public float PitchCurvePower = 0.75f; public float MinVolume = 0.25f; public float MaxVolume = 0.9f; public float SlowReplayInterval = 0.42f; public float FastReplayInterval = 0.09f; public bool RestartClipEachReplay = false; public float PlayRefreshInterval = 2f; public float ClientUpdateInterval = 0.12f; public bool StopWhenStill = true; public float StillStopDelay = 0.5f; public bool ForcePlayForTesting = false; public float ForcedTestSpeed = 4f; public bool LogStartup = true; public bool LogPlayback = false; public float LogInterval = 2f; Vector3 lastPosition; float lastSampleTime; float smoothedSpeed; float nextPlayTime; float nextPlayRefreshTime; float nextClientUpdateTime; float lastMovedTime; float nextLogTime; bool soundActive; Rigidbody rb; public override void Start() { StartCoroutine(MotionSoundLoop()); } IEnumerator MotionSoundLoop() { yield return null; yield return new WaitForSeconds(0.35f); lastPosition = transform.position; lastSampleTime = Time.time; lastMovedTime = Time.time; rb = gameObject.GetComponent(); StopTargetAudio(); if (LogStartup) { Log("Motion sound loop ready. AudioTarget=" + AudioTargetLabel() + ", Rigidbody found=" + (rb != null) + ", RestartClipEachReplay=" + RestartClipEachReplay + "."); } while (true) { yield return new WaitForSeconds(Mathf.Max(0.02f, SampleInterval)); Vector3 nowPosition = transform.position; float nowTime = Time.time; float dt = Mathf.Max(0.0001f, nowTime - lastSampleTime); float moved = Vector3.Distance(nowPosition, lastPosition); float measuredSpeed = moved / dt; if (PreferRigidbodyVelocity && rb != null) measuredSpeed = Mathf.Max(measuredSpeed, GetRigidbodySpeed(rb)); if (ForcePlayForTesting) measuredSpeed = Mathf.Max(measuredSpeed, ForcedTestSpeed); float blend = Mathf.Clamp01(VelocitySmoothing * dt); smoothedSpeed = Mathf.Lerp(smoothedSpeed, measuredSpeed, blend); bool movedEnough = moved >= MovementThreshold || ForcePlayForTesting; bool fastEnough = smoothedSpeed >= MinSpeedToPlay; if (movedEnough && fastEnough) { lastMovedTime = nowTime; float t = Mathf.InverseLerp(MinSpeedToPlay, MaxSpeedForPitch, smoothedSpeed); t = Mathf.Pow(Mathf.Clamp01(t), Mathf.Max(0.01f, PitchCurvePower)); float pitch = Mathf.Lerp(MinPitch, MaxPitch, t); float volume = Mathf.Lerp(MinVolume, MaxVolume, t); float replayInterval = Mathf.Lerp(SlowReplayInterval, FastReplayInterval, t); if (nowTime >= nextClientUpdateTime) { SetTargetPitch(pitch); SetTargetVolume(volume); nextClientUpdateTime = nowTime + Mathf.Max(0.03f, ClientUpdateInterval); } if (RestartClipEachReplay) { if (nowTime >= nextPlayTime) { StopTargetAudio(); PlayTargetAudio(); soundActive = true; nextPlayTime = nowTime + replayInterval; } } else if (!soundActive || nowTime >= nextPlayRefreshTime) { PlayTargetAudio(); soundActive = true; nextPlayRefreshTime = nowTime + Mathf.Max(0.25f, PlayRefreshInterval); } if (LogPlayback && nowTime >= nextLogTime) { Log("Motion audio active. speed=" + smoothedSpeed.ToString("0.00") + ", moved=" + moved.ToString("0.000") + ", pitch=" + pitch.ToString("0.00") + ", volume=" + volume.ToString("0.00") + ", target=" + AudioTargetLabel() + "."); nextLogTime = nowTime + Mathf.Max(0.1f, LogInterval); } } else if (StopWhenStill && soundActive && nowTime - lastMovedTime >= StillStopDelay) { StopTargetAudio(); soundActive = false; if (LogPlayback && nowTime >= nextLogTime) { Log("Motion audio stopped. speed=" + smoothedSpeed.ToString("0.00") + ", moved=" + moved.ToString("0.000") + "."); nextLogTime = nowTime + Mathf.Max(0.1f, LogInterval); } } lastPosition = nowPosition; lastSampleTime = nowTime; } } float GetRigidbodySpeed(Rigidbody body) { if (body == null) return 0f; return body.linearVelocity.magnitude; } bool HasAudioObject() { return AudioObject != null && !System.String.IsNullOrWhiteSpace(AudioObject.ObjectId); } string AudioTargetLabel() { return HasAudioObject() ? AudioObject.ObjectId : "self"; } void SetTargetPitch(float pitch) { if (HasAudioObject()) SetAudioPitch(AudioObject, pitch, null, AudioSourceIndex, IncludeAudioChildren); else SetAudioPitch(pitch, null, AudioSourceIndex, IncludeAudioChildren); } void SetTargetVolume(float volume) { if (HasAudioObject()) SetAudioVolume(AudioObject, volume, null, AudioSourceIndex, IncludeAudioChildren); else SetAudioVolume(volume, null, AudioSourceIndex, IncludeAudioChildren); } void PlayTargetAudio() { if (HasAudioObject()) PlayAudio(AudioObject, null, AudioSourceIndex, IncludeAudioChildren); else PlayAudio(null, AudioSourceIndex, IncludeAudioChildren); } void StopTargetAudio() { if (HasAudioObject()) StopAudio(AudioObject, null, AudioSourceIndex, IncludeAudioChildren); else StopAudio(null, AudioSourceIndex, IncludeAudioChildren); } }