FMOD Version: 2.02.19
Unity Version: 2022.3.55f1
Platform: Editor/Desktop
Goal: Is to use Dissonance
with fmod programmer instrument instead of bus.
Additional information: Dissonance has paid asset to implement fmod playback with bus, and it works perfectly without latency. My implementation works, but has latency.
Issue: using coreSystem.createStream
creates latency ~ 1 second, which exists even if you listen to yourself offline. Note even i remove programmer event, latency still exists.
Notes: I wont post code for AudioGenerator class as its a part of paid asset, but if needed i can upload it privately or the whole project.
private AudioGenerator _generator;
private int _sampleRate;
private GCHandle _handle;
private Sound _sound;
private FMODSoundService _audioService;
public Sound Sound => _sound;
public override float Amplitude => _generator?.Amplitude ?? 0;
protected override void OnEnable()
{
UnityEngine.Debug.Log("OnEnable");
base.OnEnable();
//Create Audio Generator
_generator = new AudioGenerator(this);
_handle = GCHandle.Alloc(_generator);
var coreSystem = RuntimeManager.CoreSystem;
coreSystem.getSoftwareFormat(out _sampleRate, out _, out _);
var exitInfo = new CREATESOUNDEXINFO
{
numchannels = 1,
format = SOUND_FORMAT.PCMFLOAT,
defaultfrequency = _sampleRate,
pcmreadcallback = SoundReadCallback,
userdata = (IntPtr)_handle,
cbsize = Marshal.SizeOf(typeof(CREATESOUNDEXINFO)),
length = (uint)(_sampleRate * sizeof(float))
};
var res = coreSystem.createStream(string.Empty, MODE.OPENUSER | MODE.LOOP_NORMAL, ref exitInfo, out _sound);
_sound.setUserData((IntPtr)_handle);
UnityEngine.Debug.Log($"after userdata set sound: {_sound.handle.ToString("X")}");
_sound.getUserData(out var testData);
UnityEngine.Debug.Log($"after userdata set data: {testData.ToString("X")}");
if(Check(res, "Failed to create FMOD sound") == false)
return;
//Play Sound
coreSystem.getMasterChannelGroup(out var masterChannel);
res = coreSystem.playSound(_sound, masterChannel, false, out var channel);
UnityEngine.Debug.Log($"played sound {res.ToString()}");
if(Check(res, "Failed to play FMOD sound") == false)
return;
}
[AOT.MonoPInvokeCallback(typeof(SOUND_PCMREAD_CALLBACK))]
private static RESULT SoundReadCallback(IntPtr sound, IntPtr data, uint datalen)
{
var soundDummy = new Sound(sound);
soundDummy.getUserData(out var userdata);
var length = datalen / sizeof(float);
if (userdata == IntPtr.Zero)
{
FloatMemClear(data, length);
return RESULT.OK;
}
// Extract a data source from the GC handle and copy the data into the DSP output buffer
var ud = GCHandle.FromIntPtr(userdata);
var audioGenerator = (AudioGenerator)ud.Target;
// If the target is null clear the buffer to silence and early exit.
if (audioGenerator != null)
{
return audioGenerator.GetAudio(data, length, 1);
}
FloatMemClear(data, length);
return RESULT.OK;
}
private static bool Check(RESULT result, string message, bool err = false)
{
if (result == RESULT.OK)
return true;
const string fmt = "{0}. FMOD Result: {1}";
if (err)
Log.Error(fmt, message, result);
else
Log.Warn(fmt, message, result);
return false;
}
private static void FloatMemClear(IntPtr buffer, uint length)
{
unsafe
{
var ptr = (float*)buffer.ToPointer();
FloatMemClear(ptr, length);
}
}
private static unsafe void FloatMemClear(float* buffer, uint length)
{
for (var i = 0; i < length; i++)
buffer[i] = 0;
}