forked from cgvr/DeltaVR
123 lines
4.3 KiB
C#
123 lines
4.3 KiB
C#
|
|
using FMOD;
|
|
using FMODUnity;
|
|
using System.Runtime.InteropServices;
|
|
using UnityEngine;
|
|
|
|
public class FMODMicLoopback : MonoBehaviour
|
|
{
|
|
private uint LATENCY_MS = 50;
|
|
private uint DRIFT_MS = 1;
|
|
|
|
private uint samplesRecorded, samplesPlayed = 0;
|
|
private int nativeRate, nativeChannels = 0;
|
|
private uint recSoundLength = 0;
|
|
uint lastPlayPos = 0;
|
|
uint lastRecordPos = 0;
|
|
private uint driftThreshold = 0;
|
|
private uint desiredLatency = 0;
|
|
private uint adjustLatency = 0;
|
|
private int actualLatency = 0;
|
|
uint minRecordDelta = 0xFFFFFFFF;
|
|
|
|
private FMOD.CREATESOUNDEXINFO exInfo = new FMOD.CREATESOUNDEXINFO();
|
|
|
|
private FMOD.Sound recSound;
|
|
private FMOD.Channel channel;
|
|
|
|
// Start is called before the first frame update
|
|
void Start()
|
|
{
|
|
/*
|
|
Determine latency in samples.
|
|
*/
|
|
FMODUnity.RuntimeManager.CoreSystem.getRecordDriverInfo(0, out _, 0, out _, out nativeRate, out _, out nativeChannels, out _);
|
|
|
|
driftThreshold = (uint)(nativeRate * DRIFT_MS) / 1000;
|
|
desiredLatency = (uint)(nativeRate * LATENCY_MS) / 1000;
|
|
adjustLatency = desiredLatency;
|
|
actualLatency = (int)desiredLatency;
|
|
|
|
/*
|
|
Create user sound to record into, then start recording.
|
|
*/
|
|
exInfo.cbsize = Marshal.SizeOf(typeof(FMOD.CREATESOUNDEXINFO));
|
|
exInfo.numchannels = nativeChannels;
|
|
exInfo.format = FMOD.SOUND_FORMAT.PCM16;
|
|
exInfo.defaultfrequency = nativeRate;
|
|
exInfo.length = (uint)(nativeRate * sizeof(short) * nativeChannels);
|
|
|
|
FMODUnity.RuntimeManager.CoreSystem.createSound("", FMOD.MODE.LOOP_NORMAL | FMOD.MODE.OPENUSER, ref exInfo, out recSound);
|
|
|
|
FMODUnity.RuntimeManager.CoreSystem.recordStart(0, recSound, true);
|
|
|
|
recSound.getLength(out recSoundLength, FMOD.TIMEUNIT.PCM);
|
|
}
|
|
|
|
// Update is called once per frame
|
|
void Update()
|
|
{
|
|
/*
|
|
Determine how much has been recorded since we last checked
|
|
*/
|
|
uint recordPos = 0;
|
|
FMODUnity.RuntimeManager.CoreSystem.getRecordPosition(0, out recordPos);
|
|
|
|
uint recordDelta = (recordPos >= lastRecordPos) ? (recordPos - lastRecordPos) : (recordPos + recSoundLength - lastRecordPos);
|
|
lastRecordPos = recordPos;
|
|
samplesRecorded += recordDelta;
|
|
|
|
if (recordDelta != 0 && (recordDelta < minRecordDelta))
|
|
{
|
|
minRecordDelta = recordDelta; // Smallest driver granularity seen so far
|
|
adjustLatency = (recordDelta <= desiredLatency) ? desiredLatency : recordDelta; // Adjust our latency if driver granularity is high
|
|
}
|
|
|
|
/*
|
|
Delay playback until our desired latency is reached.
|
|
*/
|
|
if (!channel.hasHandle() && samplesRecorded >= adjustLatency)
|
|
{
|
|
FMODUnity.RuntimeManager.CoreSystem.getMasterChannelGroup(out FMOD.ChannelGroup mCG);
|
|
FMODUnity.RuntimeManager.CoreSystem.playSound(recSound, mCG, false, out channel);
|
|
}
|
|
|
|
/*
|
|
Determine how much has been played since we last checked.
|
|
*/
|
|
if (channel.hasHandle())
|
|
{
|
|
uint playPos = 0;
|
|
channel.getPosition(out playPos, FMOD.TIMEUNIT.PCM);
|
|
|
|
uint playDelta = (playPos >= lastPlayPos) ? (playPos - lastPlayPos) : (playPos + recSoundLength - lastPlayPos);
|
|
lastPlayPos = playPos;
|
|
samplesPlayed += playDelta;
|
|
|
|
// Compensate for any drift.
|
|
int latency = (int)(samplesRecorded - samplesPlayed);
|
|
actualLatency = (int)((0.97f * actualLatency) + (0.03f * latency));
|
|
|
|
int playbackRate = nativeRate;
|
|
if (actualLatency < (int)(adjustLatency - driftThreshold))
|
|
{
|
|
// Playback position is catching up to the record position, slow playback down by 2%
|
|
playbackRate = nativeRate - (nativeRate / 50);
|
|
}
|
|
|
|
else if (actualLatency > (int)(adjustLatency + driftThreshold))
|
|
{
|
|
// Playback is falling behind the record position, speed playback up by 2%
|
|
playbackRate = nativeRate + (nativeRate / 50);
|
|
}
|
|
|
|
channel.setFrequency((float)playbackRate);
|
|
}
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
recSound.release();
|
|
}
|
|
}
|