Files
UnrealEngineUWP/Engine/Source/Runtime/AudioMixer/Private/AudioMixerSourceManager.cpp
Marc Audy e697b581a9 Copying //UE4/Dev-Framework to //UE4/Dev-Main (Source: //UE4/Dev-Framework @ 3252535)
#lockdown Nick.Penwarden
#rb none
==========================
MAJOR FEATURES + CHANGES
==========================

Change 3228282 on 2016/12/08 by Aaron.McLeran

	Adding ability to fix up existing sound classes

	- Utility "soundclassfixup" console command renames sound classes which are packaged inside other sound classes accidentally as new uniquely named packages
	- Also removes code which was allowing "NewSoundClass" behavior in sound class graphs to populate with existing sound classes. Instead, it *always* creates a new sound class and warns if the sound class already exists. Connecting existing sound classes is instead going to be done through dragging them into the graph from the content browser or from the sound class node itself.

Change 3228774 on 2016/12/09 by Ori.Cohen

	Fix multi select being very slow in phat

	#JIRA UE-39559

Change 3229036 on 2016/12/09 by Marc.Audy

	Remove trivial overrides

Change 3229130 on 2016/12/09 by Aaron.McLeran

	Fixing build error.

	Moving new code from CL 3228282 into WITH_EDITOR block since it's an editor-only operation

Change 3229412 on 2016/12/09 by Aaron.McLeran

	Fixing 7.1 surround sound systems on PC by forcing them to load as 5.1.

	- We don't support 7.1 but 7.1 systems should at least behave as good as 5.1

Change 3229782 on 2016/12/09 by Marc.Audy

	Fixed crash when seamless travelling in PIE from levels other than the current editor level with a streaming sublevel shared with the current editor level (4.15)
	#jira UE-39407

Change 3229842 on 2016/12/09 by Marc.Audy

	Missing files for CL# 3229782

Change 3229905 on 2016/12/09 by Marc.Audy

	Check Owner has a valid world before tryign to access Scene (4.14.2)
	#jira UE-39560

Change 3229961 on 2016/12/09 by Aaron.McLeran

	UE-39650 Implementing  CL 3229894 in Dev-Framework

Change 3229964 on 2016/12/09 by Aaron.McLeran

	Removing redundant loop introduced from integration

Change 3230722 on 2016/12/12 by Lukasz.Furman

	fixed vislog macros for recording thick segments
	#ue4

Change 3230864 on 2016/12/12 by Lina.Halper

	Fix crash with deleting pose

	#jira:UE-39584

Change 3230893 on 2016/12/12 by Marc.Audy

	Support more default values in UHT for FVector: ForwardVector, RightVector, and single float FVector constructor

Change 3231189 on 2016/12/12 by Ori.Cohen

	Added bone name to the physics invalid operation warnings.

Change 3231420 on 2016/12/12 by James.Golding

	Support per-component skel mesh weight override
	#jira UEFW-240

Change 3231422 on 2016/12/12 by James.Golding

	Test map for per-component skin weights

Change 3231491 on 2016/12/12 by James.Golding

	Move , FPositionVertexBuffer and FStaticMeshVertexDataInterface into their own headers
	Move FStaticMeshVertexBuffer implementation into its own cpp

Change 3231590 on 2016/12/12 by mason.seay

	Changed to box collision

Change 3231900 on 2016/12/12 by Aaron.McLeran

	Switching to creating new master submixes rather than loading them

Change 3231909 on 2016/12/12 by James.Golding

	Fix Mac CIS in StaticMeshVertexBuffer.h

Change 3232157 on 2016/12/13 by Mieszko.Zielinski

	Fixed a silly bug in FBlackboardKeySelector::InitSelection resulting in the key selector picking first "ok-ish" value, even if it wasn't matching type filter #UE4

Change 3232162 on 2016/12/13 by Mieszko.Zielinski

	Fixed UNavigationSystem::bNavigationAutoUpdateEnabled getting ignored by recent addition to related condition in UNavigationSystem #UE4

Change 3232314 on 2016/12/13 by James.Golding

	Another attempt at fixing Mac CIS

Change 3232322 on 2016/12/13 by Lukasz.Furman

	fixed order of nav area application and low area filter
	#ue4

Change 3232364 on 2016/12/13 by Thomas.Sarkanen

	Spline IK node

	Added new runtime & graph node to deform bones along a spline. Added edit mode to work with in the BP editor.
	Spline is specified within the node using control points. External spline could come later.
	Currently very expensive to evaluate as it regenerates the transformed spline and PWLA each frame.

	#jira UEFW-249 - Add spline IK node

Change 3232589 on 2016/12/13 by Thomas.Sarkanen

	Fixed non-editor builds

Change 3232654 on 2016/12/13 by Marc.Audy

	Don't rerun construction scripts when an actor has seamless traveled from another level (4.15)
	#jira UE-39699

Change 3232690 on 2016/12/13 by Martin.Wilson

	Remove unused member

Change 3232691 on 2016/12/13 by Martin.Wilson

	Virtual bone additions:

	1) Rename support
	2) Ability to chain virtual bones (Have a virtual bone that is a child of another virtual bone)

	#jira UE-39710

Change 3232782 on 2016/12/13 by Danny.Bouimad

	Adding Test Content

Change 3232843 on 2016/12/13 by danny.bouimad

	More Updates

Change 3232981 on 2016/12/13 by Marc.Audy

	Fix CIS issues

Change 3233075 on 2016/12/13 by mason.seay

	SplineIK asset for bug report

Change 3233124 on 2016/12/13 by Ori.Cohen

	Added mass automation tests.

Change 3233265 on 2016/12/13 by Ben.Marsh

	Build: Add support for building Orion and Fortnite precompiled binaries from Dev-Framework.

Change 3233365 on 2016/12/13 by mason.seay

	Resaving with non-empty engine version

Change 3233532 on 2016/12/13 by mason.seay

	Level blueprint clean up

Change 3233571 on 2016/12/13 by Ben.Marsh

	Set up paths for precompiled binaries.

Change 3233601 on 2016/12/13 by Ben.Marsh

	Build: Use the code CL rather than latest CL for precompiled binaries.

Change 3234402 on 2016/12/14 by Ori.Cohen

	Physics: Fixed line traces not working properly in editor worlds when physics substepping was enabled (UE-36408)
	- Substepping relies on interpolating transforms over frames, but only game worlds will be ticked, so we now disallow this feature in non-game worlds.
	#jira UE-36408

Change 3234415 on 2016/12/14 by Ori.Cohen

	Fix CIS

Change 3234574 on 2016/12/14 by Thomas.Sarkanen

	Fix crash when IK chain is inverted

	#jira UE-39720 - Crash compiling animation blueprint with Spline IK node

Change 3234882 on 2016/12/14 by Ori.Cohen

	Fixed teleport not working for physical animation component

Change 3234971 on 2016/12/14 by Aaron.McLeran

	Fix for omni-directional sounds in audio mixer

Change 3235251 on 2016/12/14 by mason.seay

	Assets for proposed functional testing

Change 3235492 on 2016/12/14 by Ori.Cohen

	Undo previous bad normal fix and remove wheel width compensation. This leads to bad normals when thick tires roll over the edge leading to instability.

	#JIRA UE-38710

Change 3236398 on 2016/12/15 by Marc.Audy

	(4.15) Add new object flag RF_NeedInitialization to indicate that ~FObjectInitalizer and PostInitProperties have not been executed for the object
	Do not allow Modify calls on Objects that have not been initialized
	#jira UE-39731

Change 3236413 on 2016/12/15 by Lukasz.Furman

	added EQS profiler
	#ue4

Change 3236418 on 2016/12/15 by Lukasz.Furman

	changed log verbosity in navmesh geometry export function
	#jira UE-39809
	#3039

Change 3236508 on 2016/12/15 by Ori.Cohen

	Allow vehicles to override inertia tensor after any mass properties have changed

	#JIRA UE-39566

Change 3236573 on 2016/12/15 by Ori.Cohen

	Fix manipulation tool not working properly with welded components

Change 3236577 on 2016/12/15 by Ori.Cohen

	Improve physics asset body creation so that it merges small bones and turns off collision between initially overlapping bodies.

Change 3236580 on 2016/12/15 by Ori.Cohen

	Improve mass computation for physics shapes (ignore trimesh which introduces error)

Change 3236581 on 2016/12/15 by Ori.Cohen

	Fix incorrect inertia tensor computation for cubes (was being doubled by mistake).

Change 3236809 on 2016/12/15 by Lukasz.Furman

	compilation fix: missing headers in EnvQueryManager

Change 3237187 on 2016/12/15 by Lukasz.Furman

	compilation fix: missing defines in EnvQueryInstance

Change 3237423 on 2016/12/15 by Aaron.McLeran

	Audio mixer: Allow center channel panning as a project setting.

	- To better support previous audio engine behavior, allow audio mixer to pan audio to center channel via audio settings.

Change 3237639 on 2016/12/15 by Aaron.McLeran

	Audio mixer stat tracking

Change 3237646 on 2016/12/15 by dan.reynolds

	MIDI Test Assets:

	General MIDITestBP

	MPKmini2 Child BP

	MPKmini2 Wrap Map

Change 3238148 on 2016/12/16 by Lukasz.Furman

	fixed crash in EQS profiler
	copy of CL# 3238145

Change 3238708 on 2016/12/16 by Marc.Audy

	(4.15) Don't unload and then reload streaming levels that are marked to be hidden.
	#jira UE-39883

Change 3238799 on 2016/12/16 by Lina.Halper

	Potential fix + more info on crash on copying curve for WEX

Change 3239559 on 2016/12/19 by Ori.Cohen

	Guard against infinitely thin geometry which fixes some nans

Change 3239728 on 2016/12/19 by Marc.Audy

	Merging //UE4/Dev-Main to Dev-Framework (//UE4/Dev-Framework) @ 3239536

Change 3239735 on 2016/12/19 by Jon.Nabozny

	Set 'p.MoveIgnoreFirstBlockingOverlap' to be enabled by default (3158732). This causes collision behavior to remain unchanged unless people opt in to the new behavior.
	Adjust Bot_RandomLocations default health to 100 from 0. This prevents death by hits from non-projectiles.
	4.15

	#jira UE-39387

Change 3239765 on 2016/12/19 by Jon.Nabozny

	Fix FPredictProjectilePathParams to use a valid default value for TraceChannel.
	This requires the use of a new bool bTraceWithChannel which is enabled by default.
	4.15

	#JIRA UE-39726

Change 3239810 on 2016/12/19 by Marc.Audy

	Avoid duplicate GetWorldSettings call

Change 3239826 on 2016/12/19 by Lukasz.Furman

	fixed crashes in gameplay debugger's draw delegate handling
	copy of 3234768, 3239819
	#ue4

Change 3239894 on 2016/12/19 by Richard.Hinckley

	Improving UInterface template files for "New C++ Class" feature. We now use GENERATED_BODY macros and don't need an empty constructor in the .cpp file.

Change 3239957 on 2016/12/19 by Aaron.McLeran

	UE-39924 Fix for crash when duplicating sound cue assets in content browser

	Checking for null before casting

Change 3239983 on 2016/12/19 by Mieszko.Zielinski

	Fixed injecting dynamic BTs not as expected when there's more than one injection point #UE4

Change 3240177 on 2016/12/19 by Mieszko.Zielinski

	Fix for AI agents hand-placed on levels not getting their PathFollowingComponent.MyNavData set properly #UE4

Change 3240488 on 2016/12/19 by Aaron.McLeran

	UE-39924 Fix for crash when duplicating sound cue assets in content browser

	More fixes!

Change 3240512 on 2016/12/19 by dan.reynolds

	AEOverview Update:

	- Created support for single level loads (sub-maps now auto generate lights and a staging platform when loaded individually vs. via AEOverviewMain)

	This will allow developers to load single levels functionally without adding lights or other assets to make them work.

Change 3240518 on 2016/12/19 by dan.reynolds

	AEOverview Update:

	- Added test for Multichannel 2D Reverb

Change 3240875 on 2016/12/20 by mason.seay

	Gameplay Tag Functional Tests

Change 3240876 on 2016/12/20 by dan.reynolds

	AEOverview Fix

	- Fixed miss targeted menu items (updated prefixes)

Change 3240923 on 2016/12/20 by Lukasz.Furman

	fixed memory corruption in template A* solver
	copy of CL# 3240898
	#ue4

Change 3241661 on 2016/12/21 by Thomas.Sarkanen

	Fix mesh-customized sockets not showing up by default in 'Active' socket filter mode

	#jira UE-39938 - Cannot edit mesh sockets

Change 3241964 on 2016/12/21 by Wes.Hunt

	Remove QoSReporter from CrashReportClient
	#tests editor debug gpf and verify crash is sent.

Change 3241996 on 2016/12/21 by Wes.Hunt

	Add @Owner tags to all analytics events in all our games #jira AN-805
	* Added default owners to most events. Tracked down authors of some events.
	* Added skeleton docs for many missing locations (just added @Name and @Owner so analytics folks can see the name and who to talk to in the doc webpage).
	* verified this checkin contains changes to comments ONLY.
	#tests compiled Orion and QAGame.

Change 3242825 on 2016/12/22 by Lukasz.Furman

	fixed order of behavior tree execution indices for PIE debugging
	#jira UE-39922

Change 3242860 on 2016/12/22 by mason.seay

	Functional tests for timer

Change 3243188 on 2016/12/22 by dan.reynolds

	AEOverview Update

	- Created viewport bookmarks on each sub-map for individual testing consistency

	- Updated EQ and Reverb effect parameters to work with new Audio Mixer Effects

Change 3243192 on 2016/12/22 by dan.reynolds

	AEOverview Lighting Fix

Change 3243507 on 2016/12/23 by dan.reynolds

	AEOverview Moved to Maps\Framework\Audio\

	+ redirector clean up, resaves, etc.

Change 3243553 on 2016/12/24 by Aaron.McLeran

	Bringing fixes to dev-framework from odin

	3240517
	3240476
	3240473
	3240412
	3240315
	3240220
	3240194

Change 3243567 on 2016/12/24 by Aaron.McLeran

	Fixing build.
	Adding #include for FConfigCacheIni

Change 3244466 on 2017/01/01 by Mieszko.Zielinski

	Removed FGameplayDebuggerDebugDrawDelegateHelper::InitDelegateHelper implementation that was failing a check without any explanation or comment #UE4

	#jira UE-40069

Change 3244471 on 2017/01/01 by Aaron.McLeran

	Bringing fixes to dev-framework from odin

	3244469
	3244467
	3243743

Change 3244639 on 2017/01/03 by Jurre.deBaare

	CIS error fix

Change 3244748 on 2017/01/03 by Jurre.deBaare

	Crash while using the Delete Button in the HLOD Outliner while a Generated Proxy Mesh is opened in the Static Mesh Editor
	#fix Unify path for both delete cluster options in the outliner UI
	#jira UE-40066

Change 3245338 on 2017/01/03 by Aaron.McLeran

	Getting rid of shadowed variable.

Change 3245816 on 2017/01/03 by Aaron.McLeran

	Synth component and DSP objects

	- New synth component wraps an audio component and procedural sound wave to make generating synthesis much much easier
	- Bunch of changes and improvements to DSP objects for real-time synthesis.
	- New polyphonic virtual analog synthesizer

Change 3246146 on 2017/01/04 by Ben.Marsh

	Move precompiled binaries into the Private-Binaries stream.

Change 3246283 on 2017/01/04 by Marc.Audy

	Fix CIS warnings

Change 3246457 on 2017/01/04 by Aaron.McLeran

	Fixing static analysis warnings

Change 3246519 on 2017/01/04 by Benn.Gallagher

	Fix for serialization mismatch on skeletal mesh source model.

Change 3247193 on 2017/01/04 by Dan.Reynolds

	Adding new DSP utility

Change 3247769 on 2017/01/05 by Marc.Audy

	Remove inaccurate comment

Change 3248068 on 2017/01/05 by dan.reynolds

	AEOverview Fix

	- Shortening long path name (Multichannel sub-directories) and fixing up redirectors

Change 3248251 on 2017/01/05 by Jon.Nabozny

	Fix uninitialized PropertyColor in BillboardComponent.

Change 3249305 on 2017/01/06 by James.Golding

	Fix FColorVertexBuffer copy constructor if source buffer is not initialised
	#jira UE-40242

Change 3249639 on 2017/01/06 by Jon.Nabozny

	Fix K2Node_CallFunction tool tip generation crash.

	#JIRA UE-40307

Change 3249716 on 2017/01/06 by Aaron.McLeran

	Minor changes to DSP objects

	Deciding on a method to pass parameters from BP to synth components.

Change 3249909 on 2017/01/06 by James.Golding

	Change USkinnedMeshComponent::GetSkinWeightBuffer to not require a MeshObject to return valid weight buffer
	Make VertInfluencedByActiveBoneTyped not crash if weight buffer is null
	#jira UE-40289

Change 3249931 on 2017/01/06 by Aaron.McLeran

	Bring CL 3244528 from Odin to Dev-Framework

Change 3250012 on 2017/01/06 by Aaron.McLeran

	Changing how synth params work

	- Removing base-class parameter getters/setters, removing OnParameterChange virtual function
	- Added SynthCommand function to help setting synth params on audio render thread from game thread
	- Refactored Synth1Component to use new system

Change 3250084 on 2017/01/06 by Aaron.McLeran

	Adding preset struct and adding noise to oscillator

Change 3250257 on 2017/01/07 by Aaron.McLeran

	Checking in stub for new synthesis plugin to put synthesis instances.

Change 3250264 on 2017/01/07 by Aaron.McLeran

	Moving synthesis code to new synthesis plugin

Change 3250313 on 2017/01/07 by Aaron.McLeran

	Fixing CIS static analysis warning on include cycle

Change 3250353 on 2017/01/08 by Aaron.McLeran

	Various audio mixer/dsp refinements

	-Simplying envelope code to just be a straightforward case statement
	-Added sample value lerping code for Amp object to avoid zippering when running at control-rate sample rates
	-Changed source manager wrapping code to always set NextFrameIndex to -1 in the edge case of the next being out of range, but current not being out of range. It should always be -1.
	-Added a console var to toggle enabling sample checks for tracking down sample bugs
	-Added data table row subclass to EpicSynth1Component preset struct

Change 3250382 on 2017/01/08 by Aaron.McLeran

	Bringing ODIN-3977 fix to dev-framework

Change 3250435 on 2017/01/08 by Aaron.McLeran

	Adding ability to set note durations for synth component

	Removing OnNoteOn/OnNoteOff events since derived synth components may or may not deal with notes.

Change 3250443 on 2017/01/08 by Aaron.McLeran

	Fixing CIS, removing console variable code.

Change 3250445 on 2017/01/08 by Aaron.McLeran

	Attempted fix for crash on existing PIE

Change 3250446 on 2017/01/08 by dan.reynolds

	Updated MidiSynthTestBP for new Note On Note Off functions

Change 3250447 on 2017/01/08 by dan.reynolds

	MidiListener and MidiSynthTestBP Updated to use Duration argument (MidiListener set default value to -1.0f )

Change 3250455 on 2017/01/08 by Aaron.McLeran

	Adding critical section so stopping a source voice and processing source voice can't happen at same time.

Change 3250465 on 2017/01/08 by Aaron.McLeran

	Fixing NaNs in sine approximations

Change 3250466 on 2017/01/08 by Aaron.McLeran

	Adding new music utility.

	- Changing scale indicies to be 1-based (music oriented)
	- Adding new function to get chord note from a mode

Change 3250467 on 2017/01/08 by Aaron.McLeran

	Undoing change to FastSin parabolic sine approximation

	- was not dividing by zero!

Change 3250468 on 2017/01/08 by Aaron.McLeran

	Adding ability to get a direct virtual function callback for procedural sound waves

	-Using the UE4 delegate function was not safe in the audio rendering thread and would sometimes not actually get called. Switched to a more direct and simple override, avoids some buffer copies and is more simple.

	-Updated synth component code to use the new method.

Change 3250470 on 2017/01/08 by Aaron.McLeran

	Fixing note on duration

Change 3250479 on 2017/01/08 by Aaron.McLeran

	Fixing pan in the amp dsp object

Change 3252179 on 2017/01/10 by Mieszko.Zielinski

	Fallout fix after removal of BlackboardKeyUtils::CalculateComparisonResult declaration from the AIModule #UE4

Change 3252498 on 2017/01/10 by Marc.Audy

	Fix non-unity compile errors

[CL 3252563 by Marc Audy in Main branch]
2017-01-10 14:09:16 -05:00

1045 lines
33 KiB
C++

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
#include "AudioMixerSourceManager.h"
#include "AudioMixerSource.h"
#include "AudioMixerDevice.h"
#include "AudioMixerSourceVoice.h"
#include "AudioMixerSubmix.h"
#include "IAudioExtensionPlugin.h"
#define ONEOVERSHORTMAX (3.0517578125e-5f) // 1/32768
#define VALIDATE_SOURCE_MIXER_STATE 1
namespace Audio
{
FSourceParam::FSourceParam(float InNumInterpFrames)
: StartValue(0.0f)
, EndValue(0.0f)
, CurrentValue(0.0f)
, NumInterpFrames(InNumInterpFrames)
, Frame(0.0f)
, bIsInit(true)
{
}
void FSourceParam::Reset()
{
bIsInit = true;
}
void FSourceParam::SetValue(float InValue)
{
if (bIsInit)
{
bIsInit = false;
CurrentValue = InValue;
StartValue = InValue;
EndValue = InValue;
Frame = NumInterpFrames;
}
else
{
StartValue = CurrentValue;
EndValue = InValue;
Frame = 0.0f;
}
}
float FSourceParam::Update()
{
float Alpha = Frame / NumInterpFrames;
if (Alpha >= 1.0f)
{
CurrentValue = EndValue;
return EndValue;
}
else
{
CurrentValue = FMath::Lerp(StartValue, EndValue, Alpha);
}
Frame += 1.0f;
return CurrentValue;
}
/*************************************************************************
* FMixerSourceManager
**************************************************************************/
FMixerSourceManager::FMixerSourceManager(FMixerDevice* InMixerDevice)
: MixerDevice(InMixerDevice)
, NumActiveSources(0)
, NumTotalSources(0)
, bInitialized(false)
{
}
FMixerSourceManager::~FMixerSourceManager()
{
}
void FMixerSourceManager::Init(const int32 InNumSources)
{
AUDIO_MIXER_CHECK(MixerDevice);
AUDIO_MIXER_CHECK(MixerDevice->GetSampleRate() > 0);
AUDIO_MIXER_CHECK(InNumSources > 0);
if (bInitialized)
{
return;
}
#if ENABLE_AUDIO_OUTPUT_DEBUGGING
for (int32 i = 0; i < InNumSources; ++i)
{
DebugOutputGenerators.Add(FSineOsc(44100, 50 + i * 5, 0.5f));
}
#endif
MixerSources.Init(nullptr, InNumSources);
BufferQueue.AddDefaulted(InNumSources);
BufferQueueListener.Init(nullptr, InNumSources);
NumBuffersQueued.AddDefaulted(InNumSources);
CurrentPCMBuffer.Init(nullptr, InNumSources);
CurrentAudioChunkNumFrames.AddDefaulted(InNumSources);
SourceBuffer.AddDefaulted(InNumSources);
CurrentFrameValues.AddDefaulted(InNumSources);
NextFrameValues.AddDefaulted(InNumSources);
CurrentFrameAlpha.AddDefaulted(InNumSources);
CurrentFrameIndex.AddDefaulted(InNumSources);
NumFramesPlayed.AddDefaulted(InNumSources);
float InterpFrames = MixerDevice->GetSampleRate() * 0.033f;
PitchSourceParam.Init(FSourceParam(InterpFrames), InNumSources);
VolumeSourceParam.Init(FSourceParam(InterpFrames), InNumSources);
LPFCutoffFrequencyParam.Init(FSourceParam(InterpFrames), InNumSources);
ChannelMapParam.Init(FSourceChannelMap(InterpFrames), InNumSources);
SpatParams.AddDefaulted(InNumSources);
LowPassFilters.AddDefaulted(InNumSources);
PostEffectBuffers.AddDefaulted(InNumSources);
OutputBuffer.AddDefaulted(InNumSources);
bIs3D.AddDefaulted(InNumSources);
bIsCenterChannelOnly.AddDefaulted(InNumSources);
bIsActive.AddDefaulted(InNumSources);
bIsPlaying.AddDefaulted(InNumSources);
bIsPaused.AddDefaulted(InNumSources);
bIsDone.AddDefaulted(InNumSources);
bIsBusy.AddDefaulted(InNumSources);
bUseHRTFSpatializer.AddDefaulted(InNumSources);
bHasStarted.AddDefaulted(InNumSources);
bIsDebugMode.AddDefaulted(InNumSources);
DebugName.AddDefaulted(InNumSources);
NumInputChannels.AddDefaulted(InNumSources);
NumPostEffectChannels.AddDefaulted(InNumSources);
NumInputFrames.AddDefaulted(InNumSources);
GameThreadInfo.bIsBusy.AddDefaulted(InNumSources);
GameThreadInfo.bIsDone.AddDefaulted(InNumSources);
GameThreadInfo.bNeedsSpeakerMap.AddDefaulted(InNumSources);
GameThreadInfo.bIsDebugMode.AddDefaulted(InNumSources);
GameThreadInfo.FreeSourceIndices.Reset(InNumSources);
for (int32 i = InNumSources - 1; i >= 0; --i)
{
GameThreadInfo.FreeSourceIndices.Add(i);
}
NumTotalSources = InNumSources;
bInitialized = true;
bPumpQueue = false;
}
void FMixerSourceManager::Update()
{
AUDIO_MIXER_CHECK_GAME_THREAD(MixerDevice);
#if VALIDATE_SOURCE_MIXER_STATE
for (int32 i = 0; i < NumTotalSources; ++i)
{
if (!GameThreadInfo.bIsBusy[i])
{
// Make sure that our bIsFree and FreeSourceIndices are correct
AUDIO_MIXER_CHECK(GameThreadInfo.FreeSourceIndices.Contains(i) == true);
}
}
#endif
// Pump the source command queue from the game thread to make sure
// playsound calls, param updates, etc, all happen simultaneously
bPumpQueue = true;
}
void FMixerSourceManager::ReleaseSource(const int32 SourceId)
{
AUDIO_MIXER_CHECK_AUDIO_PLAT_THREAD(MixerDevice);
AUDIO_MIXER_CHECK(SourceId < NumTotalSources);
AUDIO_MIXER_CHECK(bInitialized);
AUDIO_MIXER_CHECK(MixerSources[SourceId] != nullptr);
AUDIO_MIXER_DEBUG_LOG(SourceId, TEXT("Is releasing"));
#if AUDIO_MIXER_ENABLE_DEBUG_MODE
if (bIsDebugMode[SourceId])
{
DebugSoloSources.Remove(SourceId);
}
#endif
// Call OnRelease on the BufferQueueListener to give it a chance
// to release any resources it owns on the audio render thread
if (BufferQueueListener[SourceId])
{
BufferQueueListener[SourceId]->OnRelease();
BufferQueueListener[SourceId] = nullptr;
}
// Remove the mixer source from its submix sends
TMap<uint32, FMixerSourceSubmixSend>& SubmixSends = MixerSources[SourceId]->GetSubmixSends();
for (auto SubmixSendItem : SubmixSends)
{
SubmixSendItem.Value.Submix->RemoveSourceVoice(MixerSources[SourceId]);
}
// Delete the mixer source and null the slot
delete MixerSources[SourceId];
MixerSources[SourceId] = nullptr;
// Reset all state and data
PitchSourceParam[SourceId].Reset();
VolumeSourceParam[SourceId].Reset();
LPFCutoffFrequencyParam[SourceId].Reset();
LowPassFilters[SourceId].Reset();
ChannelMapParam[SourceId].Reset();
BufferQueue[SourceId].Empty();
CurrentPCMBuffer[SourceId] = nullptr;
CurrentAudioChunkNumFrames[SourceId] = 0;
SourceBuffer[SourceId].Reset();
CurrentFrameValues[SourceId].Reset();
NextFrameValues[SourceId].Reset();
CurrentFrameAlpha[SourceId] = 0.0f;
CurrentFrameIndex[SourceId] = 0;
NumFramesPlayed[SourceId] = 0;
PostEffectBuffers[SourceId].Reset();
OutputBuffer[SourceId].Reset();
bIs3D[SourceId] = false;
bIsCenterChannelOnly[SourceId] = false;
bIsActive[SourceId] = false;
bIsPlaying[SourceId] = false;
bIsDone[SourceId] = true;
bIsPaused[SourceId] = false;
bIsBusy[SourceId] = false;
bUseHRTFSpatializer[SourceId] = false;
bIsDone[SourceId] = false;
bHasStarted[SourceId] = false;
bIsDebugMode[SourceId] = false;
DebugName[SourceId] = FString();
NumInputChannels[SourceId] = 0;
NumPostEffectChannels[SourceId] = 0;
GameThreadInfo.bNeedsSpeakerMap[SourceId] = false;
}
bool FMixerSourceManager::GetFreeSourceId(int32& OutSourceId)
{
AUDIO_MIXER_CHECK_GAME_THREAD(MixerDevice);
if (GameThreadInfo.FreeSourceIndices.Num())
{
OutSourceId = GameThreadInfo.FreeSourceIndices.Pop();
AUDIO_MIXER_CHECK(OutSourceId < NumTotalSources);
AUDIO_MIXER_CHECK(!GameThreadInfo.bIsBusy[OutSourceId]);
AUDIO_MIXER_CHECK(!GameThreadInfo.bIsDebugMode[OutSourceId]);
AUDIO_MIXER_CHECK(NumActiveSources < NumTotalSources);
++NumActiveSources;
GameThreadInfo.bIsBusy[OutSourceId] = true;
return true;
}
AUDIO_MIXER_CHECK(false);
return false;
}
int32 FMixerSourceManager::GetNumActiveSources() const
{
return NumActiveSources;
}
void FMixerSourceManager::InitSource(const int32 SourceId, const FMixerSourceVoiceInitParams& InitParams)
{
AUDIO_MIXER_CHECK(SourceId < NumTotalSources);
AUDIO_MIXER_CHECK(GameThreadInfo.bIsBusy[SourceId]);
AUDIO_MIXER_CHECK(!GameThreadInfo.bIsDebugMode[SourceId]);
AUDIO_MIXER_CHECK(InitParams.BufferQueueListener != nullptr);
AUDIO_MIXER_CHECK_GAME_THREAD(MixerDevice);
#if AUDIO_MIXER_ENABLE_DEBUG_MODE
GameThreadInfo.bIsDebugMode[SourceId] = InitParams.bIsDebugMode;
#endif
AudioMixerThreadCommand([this, SourceId, InitParams]()
{
AUDIO_MIXER_CHECK_AUDIO_PLAT_THREAD(MixerDevice);
AUDIO_MIXER_CHECK(InitParams.SourceVoice != nullptr);
bIsPlaying[SourceId] = false;
bIsPaused[SourceId] = false;
bIsActive[SourceId] = true;
bIsBusy[SourceId] = true;
bUseHRTFSpatializer[SourceId] = InitParams.bUseHRTFSpatialization;
BufferQueueListener[SourceId] = InitParams.BufferQueueListener;
NumInputChannels[SourceId] = InitParams.NumInputChannels;
NumInputFrames[SourceId] = InitParams.NumInputFrames;
AUDIO_MIXER_CHECK(BufferQueue[SourceId].IsEmpty());
// Initialize the number of per-source LPF filters based on input channels
LowPassFilters[SourceId].AddDefaulted(InitParams.NumInputChannels);
CurrentFrameValues[SourceId].Init(0.0f, InitParams.NumInputChannels);
NextFrameValues[SourceId].Init(0.0f, InitParams.NumInputChannels);
AUDIO_MIXER_CHECK(MixerSources[SourceId] == nullptr);
MixerSources[SourceId] = InitParams.SourceVoice;
// Loop through the source's sends and add this source to those submixes with the send info
for (int32 i = 0; i < InitParams.SubmixSends.Num(); ++i)
{
const FMixerSourceSubmixSend& MixerSourceSend = InitParams.SubmixSends[i];
MixerSourceSend.Submix->AddOrSetSourceVoice(InitParams.SourceVoice, MixerSourceSend.DryLevel, MixerSourceSend.WetLevel);
}
#if AUDIO_MIXER_ENABLE_DEBUG_MODE
AUDIO_MIXER_CHECK(!bIsDebugMode[SourceId]);
bIsDebugMode[SourceId] = InitParams.bIsDebugMode;
AUDIO_MIXER_CHECK(DebugName[SourceId].IsEmpty());
DebugName[SourceId] = InitParams.DebugName;
#endif
AUDIO_MIXER_DEBUG_LOG(SourceId, TEXT("Is initializing"));
});
}
void FMixerSourceManager::ReleaseSourceId(const int32 SourceId)
{
AUDIO_MIXER_CHECK(GameThreadInfo.bIsBusy[SourceId]);
AUDIO_MIXER_CHECK_GAME_THREAD(MixerDevice);
AUDIO_MIXER_CHECK(NumActiveSources > 0);
--NumActiveSources;
GameThreadInfo.bIsBusy[SourceId] = false;
#if AUDIO_MIXER_ENABLE_DEBUG_MODE
GameThreadInfo.bIsDebugMode[SourceId] = false;
#endif
GameThreadInfo.FreeSourceIndices.Push(SourceId);
AUDIO_MIXER_CHECK(GameThreadInfo.FreeSourceIndices.Contains(SourceId));
AudioMixerThreadCommand([this, SourceId]()
{
AUDIO_MIXER_CHECK_AUDIO_PLAT_THREAD(MixerDevice);
ReleaseSource(SourceId);
});
}
void FMixerSourceManager::Play(const int32 SourceId)
{
AUDIO_MIXER_CHECK(SourceId < NumTotalSources);
AUDIO_MIXER_CHECK(GameThreadInfo.bIsBusy[SourceId]);
AUDIO_MIXER_CHECK_GAME_THREAD(MixerDevice);
AudioMixerThreadCommand([this, SourceId]()
{
AUDIO_MIXER_CHECK_AUDIO_PLAT_THREAD(MixerDevice);
bIsPlaying[SourceId] = true;
bIsPaused[SourceId] = false;
bIsActive[SourceId] = true;
AUDIO_MIXER_DEBUG_LOG(SourceId, TEXT("Is playing"));
});
}
void FMixerSourceManager::Stop(const int32 SourceId)
{
AUDIO_MIXER_CHECK(SourceId < NumTotalSources);
AUDIO_MIXER_CHECK(GameThreadInfo.bIsBusy[SourceId]);
AUDIO_MIXER_CHECK_GAME_THREAD(MixerDevice);
AudioMixerThreadCommand([this, SourceId]()
{
AUDIO_MIXER_CHECK_AUDIO_PLAT_THREAD(MixerDevice);
bIsPlaying[SourceId] = false;
bIsPaused[SourceId] = false;
bIsActive[SourceId] = false;
AUDIO_MIXER_DEBUG_LOG(SourceId, TEXT("Is stopping"));
});
}
void FMixerSourceManager::Pause(const int32 SourceId)
{
AUDIO_MIXER_CHECK(SourceId < NumTotalSources);
AUDIO_MIXER_CHECK(GameThreadInfo.bIsBusy[SourceId]);
AUDIO_MIXER_CHECK_GAME_THREAD(MixerDevice);
AudioMixerThreadCommand([this, SourceId]()
{
AUDIO_MIXER_CHECK_AUDIO_PLAT_THREAD(MixerDevice);
bIsPaused[SourceId] = true;
bIsActive[SourceId] = false;
});
}
void FMixerSourceManager::SetPitch(const int32 SourceId, const float Pitch)
{
AUDIO_MIXER_CHECK(SourceId < NumTotalSources);
AUDIO_MIXER_CHECK(GameThreadInfo.bIsBusy[SourceId]);
AudioMixerThreadCommand([this, SourceId, Pitch]()
{
AUDIO_MIXER_CHECK_AUDIO_PLAT_THREAD(MixerDevice);
PitchSourceParam[SourceId].SetValue(Pitch);
});
}
void FMixerSourceManager::SetVolume(const int32 SourceId, const float Volume)
{
AUDIO_MIXER_CHECK(SourceId < NumTotalSources);
AUDIO_MIXER_CHECK(GameThreadInfo.bIsBusy[SourceId]);
AUDIO_MIXER_CHECK_GAME_THREAD(MixerDevice);
AudioMixerThreadCommand([this, SourceId, Volume]()
{
AUDIO_MIXER_CHECK_AUDIO_PLAT_THREAD(MixerDevice);
VolumeSourceParam[SourceId].SetValue(Volume);
});
}
void FMixerSourceManager::SetSpatializationParams(const int32 SourceId, const FSpatializationParams& InParams)
{
AUDIO_MIXER_CHECK(SourceId < NumTotalSources);
AUDIO_MIXER_CHECK(GameThreadInfo.bIsBusy[SourceId]);
AUDIO_MIXER_CHECK_GAME_THREAD(MixerDevice);
AudioMixerThreadCommand([this, SourceId, InParams]()
{
AUDIO_MIXER_CHECK_AUDIO_PLAT_THREAD(MixerDevice);
SpatParams[SourceId] = InParams;
});
}
void FMixerSourceManager::SetChannelMap(const int32 SourceId, const TArray<float>& ChannelMap, const bool bInIs3D, const bool bInIsCenterChannelOnly)
{
AUDIO_MIXER_CHECK(SourceId < NumTotalSources);
AUDIO_MIXER_CHECK(GameThreadInfo.bIsBusy[SourceId]);
AUDIO_MIXER_CHECK_GAME_THREAD(MixerDevice);
AudioMixerThreadCommand([this, SourceId, ChannelMap, bInIs3D, bInIsCenterChannelOnly]()
{
AUDIO_MIXER_CHECK_AUDIO_PLAT_THREAD(MixerDevice);
// Set whether or not this is a 3d channel map and if its center channel only. Used for reseting channel maps on device change.
bIs3D[SourceId] = bInIs3D;
bIsCenterChannelOnly[SourceId] = bInIsCenterChannelOnly;
// Fix up the channel map in case device output count changed
const int32 NumSourceChannels = bUseHRTFSpatializer[SourceId] ? 2 : NumInputChannels[SourceId];
const int32 NumOutputChannels = MixerDevice->GetNumDeviceChannels();
const int32 ChannelMapSize = NumSourceChannels * NumOutputChannels;
// If this is true, then the device changed while the command was in-flight
if (ChannelMap.Num() != ChannelMapSize)
{
TArray<float> NewChannelMap;
// If 3d then just zero it out, we'll get another channel map shortly
if (bInIs3D)
{
NewChannelMap.AddZeroed(ChannelMapSize);
GameThreadInfo.bNeedsSpeakerMap[SourceId] = true;
}
// Otherwise, get an appropriate channel map for the new device configuration
else
{
MixerDevice->Get2DChannelMap(NumSourceChannels, NumOutputChannels, bInIsCenterChannelOnly, NewChannelMap);
}
ChannelMapParam[SourceId].SetChannelMap(NewChannelMap);
}
else
{
GameThreadInfo.bNeedsSpeakerMap[SourceId] = false;
ChannelMapParam[SourceId].SetChannelMap(ChannelMap);
}
});
}
void FMixerSourceManager::SetLPFFrequency(const int32 SourceId, const float InLPFFrequency)
{
AUDIO_MIXER_CHECK(SourceId < NumTotalSources);
AUDIO_MIXER_CHECK(GameThreadInfo.bIsBusy[SourceId]);
AUDIO_MIXER_CHECK_GAME_THREAD(MixerDevice);
AudioMixerThreadCommand([this, SourceId, InLPFFrequency]()
{
AUDIO_MIXER_CHECK_AUDIO_PLAT_THREAD(MixerDevice);
float SampleRate = MixerDevice->GetSampleRate();
AUDIO_MIXER_CHECK(SampleRate > 0.0f);
const float NormalizedFrequency = 2.0f * InLPFFrequency / SampleRate;
LPFCutoffFrequencyParam[SourceId].SetValue(NormalizedFrequency);
});
}
void FMixerSourceManager::SubmitBuffer(const int32 SourceId, FMixerSourceBufferPtr InSourceVoiceBuffer)
{
AUDIO_MIXER_CHECK(SourceId < NumTotalSources);
AUDIO_MIXER_CHECK(GameThreadInfo.bIsBusy[SourceId]);
AUDIO_MIXER_CHECK(InSourceVoiceBuffer->AudioBytes <= (uint32)InSourceVoiceBuffer->AudioData.Num());
AUDIO_MIXER_CHECK_GAME_THREAD(MixerDevice);
AudioMixerThreadCommand([this, SourceId, InSourceVoiceBuffer]()
{
AUDIO_MIXER_CHECK_AUDIO_PLAT_THREAD(MixerDevice);
AUDIO_MIXER_CHECK(InSourceVoiceBuffer->AudioBytes <= (uint32)InSourceVoiceBuffer->AudioData.Num());
BufferQueue[SourceId].Enqueue(InSourceVoiceBuffer);
});
}
void FMixerSourceManager::SetSubmixSendInfo(const int32 SourceId, FMixerSubmixPtr Submix, const float DryLevel, const float WetLevel)
{
AUDIO_MIXER_CHECK(SourceId < NumTotalSources);
AUDIO_MIXER_CHECK(GameThreadInfo.bIsBusy[SourceId]);
AUDIO_MIXER_CHECK_GAME_THREAD(MixerDevice);
AudioMixerThreadCommand([this, SourceId, Submix, DryLevel, WetLevel]()
{
AUDIO_MIXER_CHECK_AUDIO_PLAT_THREAD(MixerDevice);
Submix->AddOrSetSourceVoice(MixerSources[SourceId], DryLevel, WetLevel);
});
}
void FMixerSourceManager::SubmitBufferAudioThread(const int32 SourceId, FMixerSourceBufferPtr InSourceVoiceBuffer)
{
AUDIO_MIXER_CHECK(SourceId < NumTotalSources);
AUDIO_MIXER_CHECK_AUDIO_PLAT_THREAD(MixerDevice);
// Immediately push the source buffer onto the audio thread buffer queue.
BufferQueue[SourceId].Enqueue(InSourceVoiceBuffer);
}
int64 FMixerSourceManager::GetNumFramesPlayed(const int32 SourceId) const
{
AUDIO_MIXER_CHECK_GAME_THREAD(MixerDevice);
return NumFramesPlayed[SourceId];
}
bool FMixerSourceManager::IsDone(const int32 SourceId) const
{
AUDIO_MIXER_CHECK_GAME_THREAD(MixerDevice);
return GameThreadInfo.bIsDone[SourceId];
}
bool FMixerSourceManager::NeedsSpeakerMap(const int32 SourceId) const
{
AUDIO_MIXER_CHECK_GAME_THREAD(MixerDevice);
return GameThreadInfo.bNeedsSpeakerMap[SourceId];
}
void FMixerSourceManager::ReadSourceFrame(const int32 SourceId)
{
const int32 NumChannels = NumInputChannels[SourceId];
// Check if the next frame index is out of range of the total number of frames we have in our current audio buffer
bool bNextFrameOutOfRange = (CurrentFrameIndex[SourceId] + 1) >= CurrentAudioChunkNumFrames[SourceId];
bool bCurrentFrameOutOfRange = CurrentFrameIndex[SourceId] >= CurrentAudioChunkNumFrames[SourceId];
bool bReadCurrentFrame = true;
// Check the boolean conditions that determine if we need to pop buffers from our queue (in PCMRT case) *OR* loop back (looping PCM data)
while (bNextFrameOutOfRange || bCurrentFrameOutOfRange)
{
// If our current frame is in range, but next frame isn't, read the current frame now to avoid pops when transitioning between buffers
if (bNextFrameOutOfRange && !bCurrentFrameOutOfRange)
{
// Don't need to read the current frame audio after reading new audio chunk
bReadCurrentFrame = false;
AUDIO_MIXER_CHECK(CurrentPCMBuffer[SourceId].IsValid());
const int16* AudioData = (const int16*)CurrentPCMBuffer[SourceId]->AudioData.GetData();
const int32 CurrentSampleIndex = CurrentFrameIndex[SourceId] * NumChannels;
for (int32 Channel = 0; Channel < NumChannels; ++Channel)
{
CurrentFrameValues[SourceId][Channel] = (float)AudioData[CurrentSampleIndex + Channel] * ONEOVERSHORTMAX;
}
}
// If this is our first PCM buffer, we don't need to do a callback to get more audio
if (CurrentPCMBuffer[SourceId].IsValid())
{
if (CurrentPCMBuffer[SourceId]->LoopCount == Audio::LOOP_FOREVER && !CurrentPCMBuffer[SourceId]->bRealTimeBuffer)
{
AUDIO_MIXER_DEBUG_LOG(SourceId, TEXT("Hit Loop boundary, looping."));
CurrentFrameIndex[SourceId] = FMath::Max(CurrentFrameIndex[SourceId] - CurrentAudioChunkNumFrames[SourceId], 0);
break;
}
BufferQueueListener[SourceId]->OnSourceBufferEnd();
}
// If we have audio in our queue, we're still playing
if (!BufferQueue[SourceId].IsEmpty())
{
FMixerSourceBufferPtr NewBufferPtr;
BufferQueue[SourceId].Dequeue(NewBufferPtr);
CurrentPCMBuffer[SourceId] = NewBufferPtr;
AUDIO_MIXER_CHECK(MixerSources[SourceId]->NumBuffersQueued.GetValue() > 0);
MixerSources[SourceId]->NumBuffersQueued.Decrement();
CurrentAudioChunkNumFrames[SourceId] = CurrentPCMBuffer[SourceId]->AudioBytes / (NUM_BYTES_PER_SAMPLE * NumChannels);
// Subtract the number of frames in the current buffer from our frame index.
// Note: if this is the first time we're playing, CurrentFrameIndex will be 0
if (bReadCurrentFrame)
{
CurrentFrameIndex[SourceId] = FMath::Max(CurrentFrameIndex[SourceId] - CurrentAudioChunkNumFrames[SourceId], 0);
}
else
{
// Since we're not reading the current frame, we allow the current frame index to be negative (NextFrameIndex will then be 0)
// This prevents dropping a frame of audio on the buffer boundary
CurrentFrameIndex[SourceId] = -1;
}
}
else
{
if (!bIsDone[SourceId])
{
AUDIO_MIXER_DEBUG_LOG(SourceId, TEXT("Is now done."));
bIsDone[SourceId] = true;
BufferQueue[SourceId].Empty();
MixerSources[SourceId]->NumBuffersQueued.Set(0);
CurrentFrameValues[SourceId].Reset();
NextFrameValues[SourceId].Reset();
CurrentPCMBuffer[SourceId] = nullptr;
}
return;
}
bNextFrameOutOfRange = (CurrentFrameIndex[SourceId] + 1) >= CurrentAudioChunkNumFrames[SourceId];
bCurrentFrameOutOfRange = CurrentFrameIndex[SourceId] >= CurrentAudioChunkNumFrames[SourceId];
}
if (CurrentPCMBuffer[SourceId].IsValid())
{
// Grab the 16-bit PCM audio data (which could be a new audio chunk from previous ReadSourceFrame call)
const int16* AudioData = (const int16*)CurrentPCMBuffer[SourceId]->AudioData.GetData();
const int32 NextSampleIndex = (CurrentFrameIndex[SourceId] + 1) * NumChannels;
if (bReadCurrentFrame)
{
const int32 CurrentSampleIndex = CurrentFrameIndex[SourceId] * NumChannels;
for (int32 Channel = 0; Channel < NumChannels; ++Channel)
{
CurrentFrameValues[SourceId][Channel] = (float)AudioData[CurrentSampleIndex + Channel] * ONEOVERSHORTMAX;
NextFrameValues[SourceId][Channel] = (float)AudioData[NextSampleIndex + Channel] * ONEOVERSHORTMAX;
}
}
else
{
for (int32 Channel = 0; Channel < NumChannels; ++Channel)
{
NextFrameValues[SourceId][Channel] = (float)AudioData[NextSampleIndex + Channel] * ONEOVERSHORTMAX;
}
}
}
}
void FMixerSourceManager::ComputeSourceBuffers()
{
AUDIO_MIXER_CHECK_AUDIO_PLAT_THREAD(MixerDevice);
SCOPE_CYCLE_COUNTER(STAT_AudioMixerSourceBuffers);
const int32 NumFrames = MixerDevice->GetNumOutputFrames();
// Prepare the source buffers for writing source samples
for (int32 SourceId = 0; SourceId < NumTotalSources; ++SourceId)
{
const int32 NumSourceSamples = NumFrames*NumInputChannels[SourceId];
SourceBuffer[SourceId].Reset(NumSourceSamples);
}
// Local variable used for sample values
float SampleValue = 0.0f;
for (int32 SourceId = 0; SourceId < NumTotalSources; ++SourceId)
{
if (!bIsBusy[SourceId] || !bIsPlaying[SourceId] || bIsPaused[SourceId])
{
continue;
}
for (int32 Frame = 0; Frame < NumFrames; ++Frame)
{
// If the source is done, then we'll just write out 0s
if (!bIsDone[SourceId])
{
// Whether or not we need to read another sample from the source buffers
// If we haven't yet played any frames, then we will need to read the first source samples no matter what
bool bReadNextSample = !bHasStarted[SourceId];
// Reset that we've started generating audio
bHasStarted[SourceId] = true;
// Update the PrevFrameIndex value for the source based on alpha value
while (CurrentFrameAlpha[SourceId] >= 1.0f)
{
// Our inter-frame alpha lerping value is causing us to read new source frames
bReadNextSample = true;
// Bump up the current frame index
CurrentFrameIndex[SourceId]++;
// Bump up the frames played -- this is tracking the total frames in source file played
// CurrentFrameIndex can wrap for looping sounds so won't be accurate in that case
NumFramesPlayed[SourceId]++;
CurrentFrameAlpha[SourceId] -= 1.0f;
}
// If our alpha parameter caused us to jump to a new source frame, we need
// read new samples into our prev and next frame sample data
if (bReadNextSample)
{
ReadSourceFrame(SourceId);
}
}
const int32 NumSourceChannels = NumInputChannels[SourceId];
// If we've finished reading all buffer data, then just write out 0s
if (bIsDone[SourceId])
{
for (int32 Channel = 0; Channel < NumSourceChannels; ++Channel)
{
SourceBuffer[SourceId].Add(0.0f);
}
}
else
{
// Get the volume value of the source at this frame index
for (int32 Channel = 0; Channel < NumSourceChannels; ++Channel)
{
const float CurrFrameValue = CurrentFrameValues[SourceId][Channel];
const float NextFrameValue = NextFrameValues[SourceId][Channel];
const float CurrentAlpha = CurrentFrameAlpha[SourceId];
SampleValue = FMath::Lerp(CurrFrameValue, NextFrameValue, CurrentAlpha);
SourceBuffer[SourceId].Add(SampleValue);
}
const float CurrentPitchScale = PitchSourceParam[SourceId].Update();
CurrentFrameAlpha[SourceId] += CurrentPitchScale;
}
}
}
}
void FMixerSourceManager::ComputePostSourceEffectBuffers()
{
AUDIO_MIXER_CHECK_AUDIO_PLAT_THREAD(MixerDevice);
SCOPE_CYCLE_COUNTER(STAT_AudioMixerSourceEffectBuffers);
// First run each source buffer through it's default source effects (e.g. LPF)
const int32 NumFrames = MixerDevice->GetNumOutputFrames();
class IAudioSpatializationAlgorithm* SpatializeProcessor = MixerDevice->SpatializeProcessor;
const bool bIsDebugModeEnabled = DebugSoloSources.Num() > 0;
for (int32 SourceId = 0; SourceId < NumTotalSources; ++SourceId)
{
if (!bIsBusy[SourceId] || !bIsPlaying[SourceId] || bIsPaused[SourceId])
{
continue;
}
// Get the source buffer
TArray<float>& Buffer = SourceBuffer[SourceId];
// First apply the LPF filter (and any other default per-source effecct) to the source before spatializing
FSourceParam& LPFFrequencyParam = LPFCutoffFrequencyParam[SourceId];
const int32 NumInputChans = NumInputChannels[SourceId];
int32 SampleIndex = 0;
for (int32 Frame = 0; Frame < NumFrames; ++Frame)
{
const float LPFFreq = LPFFrequencyParam.Update();
float CurrentVolumeValue = VolumeSourceParam[SourceId].Update();
#if AUDIO_MIXER_ENABLE_DEBUG_MODE
if (bIsDebugModeEnabled && !bIsDebugMode[SourceId])
{
CurrentVolumeValue *= 0.0f;
}
#endif
for (int32 Channel = 0; Channel < NumInputChans; ++Channel)
{
SampleIndex = NumInputChans * Frame + Channel;
// Update the frequency
LowPassFilters[SourceId][Channel].SetFrequency(LPFFreq);
// Process the source through the filter
float SourceSample = Buffer[SampleIndex];
SourceSample = LowPassFilters[SourceId][Channel].ProcessAudio(SourceSample);
// Process the effect chain
// TODO
// SourceSample = MyEffect[SourceId][Channel](SourceSample);
SourceSample *= CurrentVolumeValue;
// Write back out the sample after the effect processing and volume scaling
Buffer[SampleIndex] = SourceSample;
}
}
// If the source has HRTF processing enabled, run it through the spatializer
if (bUseHRTFSpatializer[SourceId])
{
AUDIO_MIXER_CHECK(SpatializeProcessor);
AUDIO_MIXER_CHECK(NumInputChannels[SourceId] == 1);
// Reset our scratch buffer and make sure it has enough data to hold 2 channels of interleaved data
ScratchBuffer.Reset();
ScratchBuffer.AddZeroed(2 * NumFrames);
FSpatializationParams& SourceSpatParams = SpatParams[SourceId];
SpatializeProcessor->SetSpatializationParameters(SourceId, SourceSpatParams);
SpatializeProcessor->ProcessSpatializationForVoice(SourceId, Buffer.GetData(), ScratchBuffer.GetData());
// We are now a 2-channel file and should not be spatialized using normal 3d spatialization
NumPostEffectChannels[SourceId] = 2;
// Copy the output scratch buffer to the post-effect buffers
PostEffectBuffers[SourceId] = ScratchBuffer;
}
else
{
PostEffectBuffers[SourceId] = Buffer;
// Otherwise our pre- and post-effect channels are the same as the input channels
NumPostEffectChannels[SourceId] = NumInputChannels[SourceId];
}
}
}
void FMixerSourceManager::ComputeOutputBuffers()
{
AUDIO_MIXER_CHECK_AUDIO_PLAT_THREAD(MixerDevice);
SCOPE_CYCLE_COUNTER(STAT_AudioMixerSourceOutputBuffers);
const int32 NumFrames = MixerDevice->GetNumOutputFrames();
const int32 NumOutputChannels = MixerDevice->GetNumDeviceChannels();
// Reset the dry/wet buffers for all the sources
const int32 NumOutputSamples = NumFrames * NumOutputChannels;
for (int32 SourceId = 0; SourceId < NumTotalSources; ++SourceId)
{
OutputBuffer[SourceId].Reset();
OutputBuffer[SourceId].AddZeroed(NumOutputSamples);
}
for (int32 SourceId = 0; SourceId < NumTotalSources; ++SourceId)
{
// Don't need to compute anything if the source is not playing or paused (it will remain at 0.0 volume)
// Note that effect chains will still be able to continue to compute audio output. The source output
// will simply stop being read from.
if (!bIsBusy[SourceId] || !bIsPlaying[SourceId] || bIsPaused[SourceId])
{
continue;
}
for (int32 Frame = 0; Frame < NumFrames; ++Frame)
{
const int32 PostEffectChannels = NumPostEffectChannels[SourceId];
#if ENABLE_AUDIO_OUTPUT_DEBUGGING
FSineOsc& SineOsc = DebugOutputGenerators[SourceId];
float SourceSampleValue = SineOsc();
#else
float SourceSampleValue = 0.0f;
#endif
// For each source channel, compute the output channel mapping
for (int32 SourceChannel = 0; SourceChannel < PostEffectChannels; ++SourceChannel)
{
#if !ENABLE_AUDIO_OUTPUT_DEBUGGING
const int32 SourceSampleIndex = Frame * PostEffectChannels + SourceChannel;
SourceSampleValue = PostEffectBuffers[SourceId][SourceSampleIndex];
#endif
// Make sure that our channel map is appropriate for the source channel and output channel count!
ChannelMapParam[SourceId].GetChannelMap(ScratchChannelMap);
AUDIO_MIXER_CHECK(ScratchChannelMap.Num() == PostEffectChannels * NumOutputChannels);
for (int32 OutputChannel = 0; OutputChannel < NumOutputChannels; ++OutputChannel)
{
// Look up the channel map value (maps input channels to output channels) for the source
// This is the step that either applies the spatialization algorithm or just maps a 2d sound
const int32 ChannelMapIndex = NumOutputChannels * SourceChannel + OutputChannel;
const float ChannelMapValue = ScratchChannelMap[ChannelMapIndex];
// If we have a non-zero sample value, write it out. Note that most 3d audio channel maps
// for surround sound will result in 0.0 sample values so this branch should save a bunch of multiplies + adds
if (ChannelMapValue > 0.0f)
{
AUDIO_MIXER_CHECK(ChannelMapValue >= 0.0f && ChannelMapValue <= 1.0f);
// Scale the input source sample for this source channel value
const float SampleValue = SourceSampleValue * ChannelMapValue;
const int32 OutputSampleIndex = Frame * NumOutputChannels + OutputChannel;
OutputBuffer[SourceId][OutputSampleIndex] += SampleValue;
}
}
}
}
}
}
void FMixerSourceManager::MixOutputBuffers(const int32 SourceId, TArray<float>& OutDryBuffer, TArray<float>& OutWetBuffer, const float DryLevel, const float WetLevel) const
{
AUDIO_MIXER_CHECK_AUDIO_PLAT_THREAD(MixerDevice);
AUDIO_MIXER_CHECK(SourceId < NumTotalSources);
if (DryLevel > 0.0f)
{
for (int32 SampleIndex = 0; SampleIndex < OutDryBuffer.Num(); ++SampleIndex)
{
OutDryBuffer[SampleIndex] += OutputBuffer[SourceId][SampleIndex] * DryLevel;
}
}
if (WetLevel > 0.0f)
{
for (int32 SampleIndex = 0; SampleIndex < OutDryBuffer.Num(); ++SampleIndex)
{
OutWetBuffer[SampleIndex] += OutputBuffer[SourceId][SampleIndex] * WetLevel;
}
}
}
void FMixerSourceManager::UpdateDeviceChannelCount(const int32 InNumOutputChannels)
{
// Update all source's to appropriate channel maps
for (int32 SourceId = 0; SourceId < NumTotalSources; ++SourceId)
{
// Don't need to do anything if it's not active
if (!bIsActive[SourceId])
{
continue;
}
ScratchChannelMap.Reset();
const int32 NumSoureChannels = bUseHRTFSpatializer[SourceId] ? 2 : NumInputChannels[SourceId];
// If this is a 3d source, then just zero out the channel map, it'll cause a temporary blip
// but it should reset in the next tick
if (bIs3D[SourceId])
{
GameThreadInfo.bNeedsSpeakerMap[SourceId] = true;
ScratchChannelMap.AddZeroed(NumSoureChannels * InNumOutputChannels);
}
// If it's a 2D sound, then just get a new channel map appropriate for the new device channel count
else
{
ScratchChannelMap.Reset();
MixerDevice->Get2DChannelMap(NumSoureChannels, InNumOutputChannels, bIsCenterChannelOnly[SourceId], ScratchChannelMap);
}
ChannelMapParam[SourceId].SetChannelMap(ScratchChannelMap);
}
}
void FMixerSourceManager::ComputeNextBlockOfSamples()
{
AUDIO_MIXER_CHECK_AUDIO_PLAT_THREAD(MixerDevice);
SCOPE_CYCLE_COUNTER(STAT_AudioMixerSourceManagerUpdate);
if (bPumpQueue)
{
bPumpQueue = false;
PumpCommandQueue();
}
// Get the next block of frames from the source buffers
ComputeSourceBuffers();
// Compute the audio source buffers after their individual effect chain processing
ComputePostSourceEffectBuffers();
// Get the audio for the output buffers
ComputeOutputBuffers();
// Update the game thread copy of source doneness
for (int32 SourceId = 0; SourceId < NumTotalSources; ++SourceId)
{
GameThreadInfo.bIsDone[SourceId] = bIsDone[SourceId];
}
}
void FMixerSourceManager::AudioMixerThreadCommand(TFunction<void()> InFunction)
{
// Add the function to the command queue
SourceCommandQueue.Enqueue(MoveTemp(InFunction));
}
void FMixerSourceManager::PumpCommandQueue()
{
AUDIO_MIXER_CHECK_AUDIO_PLAT_THREAD(MixerDevice);
// Pop and execute all the commands that came since last update tick
TFunction<void()> CommandFunction;
while (SourceCommandQueue.Dequeue(CommandFunction))
{
CommandFunction();
}
}
}