// Copyright (c), Firelight Technologies Pty, Ltd. 2024-2024. #include "FMODAudioLinkSynchronizer.h" #include "fmod_studio.hpp" #include "FMODStudioModule.h" #include "FMODAudioLinkLog.h" #include "AudioDeviceManager.h" FMOD_RESULT F_CALLBACK MixCallback(FMOD_SYSTEM* system, FMOD_SYSTEM_CALLBACK_TYPE type, void* commanddata1, void* commanddata2, void* userdata) { FFMODAudioLinkSynchronizer *Synchro = static_cast(userdata); if (Synchro) { FMOD::System *CoreSystem = (FMOD::System*)system; FMOD::ChannelGroup *MasterGroup = NULL; CoreSystem->getMasterChannelGroup(&MasterGroup); uint64 dspClock = 0; MasterGroup->getDSPClock(&dspClock, 0); switch (type) { case FMOD_SYSTEM_CALLBACK_POSTMIX: Synchro->ExecuteEndRender(dspClock); break; default: break; } } return FMOD_OK; } FFMODAudioLinkSynchronizer::FFMODAudioLinkSynchronizer() { Bind(); } FFMODAudioLinkSynchronizer::~FFMODAudioLinkSynchronizer() { Unbind(); } void FFMODAudioLinkSynchronizer::ExecuteEndRender(uint64 dspClock) { FScopeLock Lock(&CallbackLock); FOnRenderParams Params; Params.BufferTickID = dspClock; Params.NumFrames = dspBufferSize; OnEndRender.Broadcast(Params); } void FFMODAudioLinkSynchronizer::ExecuteOpenStream() { UE_LOG(LogFMODAudioLink, Verbose, TEXT("FFMODAudioLinkSynchronizer::ExecuteOpenStream: Opening stream between Unreal and FMOD.")); int samplerate = 0, numchannels = 0; FMOD_SPEAKERMODE speakerMode; CoreSystem->getDSPBufferSize(&dspBufferSize, 0); CoreSystem->getSoftwareFormat(&samplerate, &speakerMode, 0); CoreSystem->getSpeakerModeChannels(speakerMode, &numchannels); FOnOpenStreamParams Params; Params.NumChannels = numchannels; Params.SampleRate = samplerate; Params.NumFrames = dspBufferSize; Params.Name = TEXT("AudioLink for FMOD"); OnOpenStream.Broadcast(Params); } void FFMODAudioLinkSynchronizer::ExecuteCloseStream() { UE_LOG(LogFMODAudioLink, Verbose, TEXT("FFMODAudioLinkSynchronizer::ExecuteCloseStream: Closing stream between Unreal and FMOD.")); OnCloseStream.Broadcast(); } void FFMODAudioLinkSynchronizer::ExecuteSuspend() { OnSuspend.Broadcast(); } void FFMODAudioLinkSynchronizer::ExecuteResume() { OnResume.Broadcast(); } void FFMODAudioLinkSynchronizer::Bind() { if (UNLIKELY(bIsBound)) { UE_LOG(LogFMODAudioLink, Error, TEXT("FFMODAudioLinkSynchronizer::Bind: Already bound.")); return; } if (UNLIKELY(!IFMODStudioModule::Get().IsAvailable())) { UE_LOG(LogFMODAudioLink, Error, TEXT("FFMODAduioLinkSynchronizer::Bind: FMODStudioModule not available.")); return; } auto* StudioSystem = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Runtime); if (!StudioSystem) { StudioSystem = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Auditioning); } if (UNLIKELY(!StudioSystem)) { UE_LOG(LogFMODAudioLink, Error, TEXT("FFMODAudioLinkSynchronizer::ExecuteOpenStream: No Studio System.")); return; } StudioSystem->getCoreSystem(&CoreSystem); UE_LOG(LogFMODAudioLink, Verbose, TEXT("FFMODAudioLinkSynchronizer::Bind: Binding System Callbacks.")); CoreSystem->setUserData(this); CoreSystem->setCallback(MixCallback, FMOD_SYSTEM_CALLBACK_POSTMIX); ExecuteOpenStream(); bIsBound = true; } void FFMODAudioLinkSynchronizer::Unbind() { if (UNLIKELY(!bIsBound)) { return; } if (UNLIKELY(!CoreSystem)) { return; } UE_LOG(LogFMODAudioLink, Verbose, TEXT("FFMODAudioLinkSynchronizer::Bind: Unbinding.")); CoreSystem->setCallback(NULL, 0); bIsBound = false; ExecuteCloseStream(); }