1
0
forked from cgvr/DeltaVR

radio particle emission rate corresponds with mic input volume

This commit is contained in:
2026-03-06 17:41:52 +02:00
parent 1bef5a6815
commit c0e7f292a0
4 changed files with 99 additions and 10 deletions

View File

@@ -1074,7 +1074,7 @@ ParticleSystem:
rateOverTime: rateOverTime:
serializedVersion: 2 serializedVersion: 2
minMaxState: 0 minMaxState: 0
scalar: 50 scalar: 3
minScalar: 10 minScalar: 10
maxCurve: maxCurve:
serializedVersion: 2 serializedVersion: 2
@@ -5310,6 +5310,8 @@ MonoBehaviour:
radioButton: {fileID: 1588960997487099811} radioButton: {fileID: 1588960997487099811}
computerScreen: {fileID: 0} computerScreen: {fileID: 0}
particles: {fileID: 4781044067799935830} particles: {fileID: 4781044067799935830}
minimumEmissionRate: 3
maximumEmissionRate: 50
--- !u!114 &198665210576292642 --- !u!114 &198665210576292642
MonoBehaviour: MonoBehaviour:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@@ -64,6 +64,11 @@ public class FMODWhisperBridge : MonoBehaviour
private bool isRecordingActivated = false; private bool isRecordingActivated = false;
private bool _skipOneFeedFrame = false; private bool _skipOneFeedFrame = false;
// --- Speech Volume Measurement ---
private float currentVolumeRms = 0f; // Smoothed RMS for external access
private float volumeSmoothing = 0.15f; // How fast the meter reacts (0.10.3 good)
private void Awake() private void Awake()
{ {
if (!whisper) whisper = FindObjectOfType<WhisperManager>(); if (!whisper) whisper = FindObjectOfType<WhisperManager>();
@@ -254,6 +259,29 @@ public class FMODWhisperBridge : MonoBehaviour
Debug.Log("[FMOD→Whisper] Stream deactivated (Whisper stopped; FMOD still recording)."); Debug.Log("[FMOD→Whisper] Stream deactivated (Whisper stopped; FMOD still recording).");
} }
/// <summary>
/// Returns current microphone level in dBFS (decibels relative to full-scale).
/// 0 dBFS = digital clipping; normal speech is typically around -35 to -20 dBFS.
/// </summary>
public float GetCurrentVolumeDb()
{
// Guard from log(0)
const float minRms = 1e-7f;
float rms = Mathf.Clamp(currentVolumeRms, minRms, 1f);
return 20f * Mathf.Log10(rms);
}
/// <summary>
/// Returns a UI-friendly 0..1 loudness from the current dBFS value.
/// Adjust the dB range to your content/environment if needed.
/// </summary>
public float GetNormalizedVolume01()
{
float db = GetCurrentVolumeDb(); // typically ~ -60 .. -15 during use
return Mathf.Clamp01(Mathf.InverseLerp(-60f, -15f, db));
}
private void Update() private void Update()
{ {
// Always tick FMOD // Always tick FMOD
@@ -295,11 +323,35 @@ public class FMODWhisperBridge : MonoBehaviour
try try
{ {
float rmsAccumulator = 0f;
int rmsSampleCount = 0;
// 1) Measure volume
if (len1 > 0)
{
ComputeRmsFromPcm16(p1, len1, ref rmsAccumulator, ref rmsSampleCount);
}
if (len2 > 0)
{
ComputeRmsFromPcm16(p2, len2, ref rmsAccumulator, ref rmsSampleCount);
}
if (rmsSampleCount > 0)
{
float rms = Mathf.Sqrt(rmsAccumulator / rmsSampleCount);
// Smooth the value
currentVolumeRms = Mathf.Lerp(currentVolumeRms, rms, 1f - Mathf.Pow(1f - volumeSmoothing, Time.deltaTime * 60f));
}
// 2) Feed audio to Whisper ONLY when active
if (shouldFeed && !_skipOneFeedFrame) if (shouldFeed && !_skipOneFeedFrame)
{ {
if (len1 > 0) CopyPcm16ToFloatAndFeed(p1, len1); if (len1 > 0) CopyPcm16ToFloatAndFeed(p1, len1);
if (len2 > 0) CopyPcm16ToFloatAndFeed(p2, len2); if (len2 > 0) CopyPcm16ToFloatAndFeed(p2, len2);
} }
// If skipping, we just discard this frame to ensure no stale data leaks. // If skipping, we just discard this frame to ensure no stale data leaks.
} }
finally finally
@@ -427,4 +479,30 @@ public class FMODWhisperBridge : MonoBehaviour
} }
} }
/// <summary>
/// Computes RMS (root mean square) from a PCM16 block using only safe code.
/// Uses the shared _shortOverlay buffer (no allocations).
/// Accumulates results into accumulator + sampleCount.
/// </summary>
private void ComputeRmsFromPcm16(IntPtr src, uint byteLen, ref float accumulator, ref int sampleCount)
{
// Number of PCM16 samples (2 bytes per sample)
int samples = (int)(byteLen / 2);
if (samples <= 0) return;
// Ensure overlay buffer exists & is large enough
EnsureShortOverlay(samples, out short[] sBuf);
// Copy PCM16 into managed buffer (safe)
Marshal.Copy(src, sBuf, 0, samples);
// Accumulate squared amplitude
for (int i = 0; i < samples; i++)
{
float v = sBuf[i] / 32768f; // normalize to [-1..1]
accumulator += v * v;
}
sampleCount += samples;
}
} }

View File

@@ -14,9 +14,14 @@ public class RadioTransmitter : XRGrabInteractable
public FMODWhisperBridge fmodWhisperBridge; public FMODWhisperBridge fmodWhisperBridge;
public ReleasableButton radioButton; public ReleasableButton radioButton;
public TextMeshProUGUI computerScreen; public TextMeshProUGUI computerScreen;
[Header("Particle System Config")]
public ParticleSystem particles; public ParticleSystem particles;
public float baseEmissionRate = 3;
public float maxExtraEmissionRate = 50;
private bool isProcessing; private bool isProcessing;
private ParticleSystem.EmissionModule particleEmission;
// Start is called before the first frame update // Start is called before the first frame update
void Start() void Start()
@@ -26,14 +31,20 @@ public class RadioTransmitter : XRGrabInteractable
radioButton.Lock(); radioButton.Lock();
isProcessing = false; isProcessing = false;
var emission = particles.emission; particleEmission = particles.emission;
emission.enabled = false; particleEmission.enabled = false;
} }
// Update is called once per frame // Update is called once per frame
void Update() void Update()
{ {
particles.transform.LookAt(computerScreen.transform.position); if (particles.emission.enabled)
{
particles.transform.LookAt(computerScreen.transform.position);
Debug.Log("speech volume normalized: " + fmodWhisperBridge.GetNormalizedVolume01());
Debug.Log("speech volume decibels: " + fmodWhisperBridge.GetCurrentVolumeDb());
particleEmission.rateOverTime = baseEmissionRate + fmodWhisperBridge.GetNormalizedVolume01() * maxExtraEmissionRate;
}
} }
protected async override void OnSelectEntered(SelectEnterEventArgs args) protected async override void OnSelectEntered(SelectEnterEventArgs args)
@@ -55,8 +66,7 @@ public class RadioTransmitter : XRGrabInteractable
fmodWhisperBridge.OnWhisperSegmentFinished += OnPlayerSpeechFinished; fmodWhisperBridge.OnWhisperSegmentFinished += OnPlayerSpeechFinished;
fmodWhisperBridge.ActivateRecording(); fmodWhisperBridge.ActivateRecording();
var emission = particles.emission; particleEmission.enabled = true;
emission.enabled = true;
} }
} }
@@ -81,7 +91,6 @@ public class RadioTransmitter : XRGrabInteractable
fmodWhisperBridge.OnWhisperSegmentFinished -= OnPlayerSpeechFinished; fmodWhisperBridge.OnWhisperSegmentFinished -= OnPlayerSpeechFinished;
fmodWhisperBridge.DeactivateRecording(); fmodWhisperBridge.DeactivateRecording();
var emission = particles.emission; particleEmission.enabled = false;
emission.enabled = false;
} }
} }