forked from cgvr/DeltaVR
radio particle emission rate corresponds with mic input volume
This commit is contained in:
Binary file not shown.
@@ -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
|
||||||
|
|||||||
@@ -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.1–0.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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user