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

@@ -64,6 +64,11 @@ public class FMODWhisperBridge : MonoBehaviour
private bool isRecordingActivated = 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()
{
if (!whisper) whisper = FindObjectOfType<WhisperManager>();
@@ -254,6 +259,29 @@ public class FMODWhisperBridge : MonoBehaviour
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()
{
// Always tick FMOD
@@ -295,11 +323,35 @@ public class FMODWhisperBridge : MonoBehaviour
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 (len1 > 0) CopyPcm16ToFloatAndFeed(p1, len1);
if (len2 > 0) CopyPcm16ToFloatAndFeed(p2, len2);
}
// If skipping, we just discard this frame to ensure no stale data leaks.
}
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 ReleasableButton radioButton;
public TextMeshProUGUI computerScreen;
[Header("Particle System Config")]
public ParticleSystem particles;
public float baseEmissionRate = 3;
public float maxExtraEmissionRate = 50;
private bool isProcessing;
private ParticleSystem.EmissionModule particleEmission;
// Start is called before the first frame update
void Start()
@@ -26,14 +31,20 @@ public class RadioTransmitter : XRGrabInteractable
radioButton.Lock();
isProcessing = false;
var emission = particles.emission;
emission.enabled = false;
particleEmission = particles.emission;
particleEmission.enabled = false;
}
// Update is called once per frame
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)
@@ -55,8 +66,7 @@ public class RadioTransmitter : XRGrabInteractable
fmodWhisperBridge.OnWhisperSegmentFinished += OnPlayerSpeechFinished;
fmodWhisperBridge.ActivateRecording();
var emission = particles.emission;
emission.enabled = true;
particleEmission.enabled = true;
}
}
@@ -81,7 +91,6 @@ public class RadioTransmitter : XRGrabInteractable
fmodWhisperBridge.OnWhisperSegmentFinished -= OnPlayerSpeechFinished;
fmodWhisperBridge.DeactivateRecording();
var emission = particles.emission;
emission.enabled = false;
particleEmission.enabled = false;
}
}