Skip to content

Commit

Permalink
Add surround to FMOD plugins (ValveSoftware#329)
Browse files Browse the repository at this point in the history
* Add surround sound to FMOD plugin

* Fix compile issue on Windows

* Reallocate buffers when channel count increases

* Handle return value from allocation

* Rebuild processing effects on channel changes

* Apply proper reinitializations to reverb and return effect

* Implement surround format controls and add to UI

* Reassign buffers after channel number decreases

* Implement automatic global HRTF setting for FMOD plugins

* Only render binaural when output channels are stereo

* Feature parity for Unity plugins

+ Implement automatic global HRTF setting for FMOD plugins
+ Only render binaural when output channels are stereo

* Improve naming consistency

* Remove global HRTF disable option
  • Loading branch information
Schroedingers-Cat authored May 20, 2024
1 parent c3bfae8 commit 4d76d72
Show file tree
Hide file tree
Showing 12 changed files with 417 additions and 48 deletions.
105 changes: 97 additions & 8 deletions fmod/src/mix_return_effect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,32 @@ enum Params
*/
BINAURAL,

/**
* **Type**: `FMOD_DSP_PARAMETER_TYPE_INT`
*
* **Range**: 0 to 2.
*
* Controls the output format.
*
* - `0`: Output will be the format in FMOD's mixer.
* - `1`: Output will be the format from FMOD's final output.
* - `2`: Output will be the format from the event's input.
*/
OUTPUT_FORMAT,

/** The number of parameters in this effect. */
NUM_PARAMS
};

FMOD_DSP_PARAMETER_DESC gParams[] = {
{ FMOD_DSP_PARAMETER_TYPE_BOOL, "Binaural", "", "Spatialize reflected sound using HRTF." }
{ FMOD_DSP_PARAMETER_TYPE_BOOL, "Binaural", "", "Spatialize reflected sound using HRTF." },
{ FMOD_DSP_PARAMETER_TYPE_INT, "OutputFormat", "", "Output Format" },
};

FMOD_DSP_PARAMETER_DESC* gParamsArray[NUM_PARAMS];

const char* gOutputFormatValues[] = { "From Mixer", "From Final Out", "From Input" };

void initParamDescs()
{
for (auto i = 0; i < NUM_PARAMS; ++i)
Expand All @@ -51,18 +67,22 @@ void initParamDescs()
}

gParams[BINAURAL].booldesc = {false};
gParams[OUTPUT_FORMAT].intdesc = { 0, 2, 0, false, gOutputFormatValues };
}

struct State
{
bool binaural;
ParameterSpeakerFormatType outputFormat;

IPLAudioBuffer reflectionsBuffer;
IPLAudioBuffer inBuffer;
IPLAudioBuffer outBuffer;

IPLReflectionMixer reflectionMixer;
IPLReflectionEffectSettings reflectionMixerSettingsBackup;
IPLAmbisonicsDecodeEffect ambisonicsEffect;
IPLAmbisonicsDecodeEffectSettings ambisonicsEffectSettingsBackup;
};

enum InitFlags
Expand Down Expand Up @@ -102,6 +122,12 @@ InitFlags lazyInit(FMOD_DSP_STATE* state,
{
status = IPL_STATUS_SUCCESS;

if (effect->reflectionMixer && effect->reflectionMixerSettingsBackup.numChannels != numChannelsForOrder(gSimulationSettings.maxOrder))
{
iplReflectionMixerReset(effect->reflectionMixer);
iplReflectionMixerRelease(&effect->reflectionMixer);
}

if (!effect->reflectionMixer)
{
IPLReflectionEffectSettings effectSettings;
Expand All @@ -110,6 +136,8 @@ InitFlags lazyInit(FMOD_DSP_STATE* state,

status = iplReflectionMixerCreate(gContext, &audioSettings, &effectSettings, &effect->reflectionMixer);

effect->reflectionMixerSettingsBackup = effectSettings;

if (!gNewReflectionMixerWritten)
{
iplReflectionMixerRelease(&gReflectionMixer[1]);
Expand All @@ -127,6 +155,12 @@ InitFlags lazyInit(FMOD_DSP_STATE* state,
{
status = IPL_STATUS_SUCCESS;

if (effect->ambisonicsEffect && effect->ambisonicsEffectSettingsBackup.speakerLayout.type != speakerLayoutForNumChannels(numChannelsOut).type)
{
iplAmbisonicsDecodeEffectReset(effect->ambisonicsEffect);
iplAmbisonicsDecodeEffectRelease(&effect->ambisonicsEffect);
}

if (!effect->ambisonicsEffect)
{
IPLAmbisonicsDecodeEffectSettings effectSettings;
Expand All @@ -135,6 +169,8 @@ InitFlags lazyInit(FMOD_DSP_STATE* state,
effectSettings.maxOrder = gSimulationSettings.maxOrder;

status = iplAmbisonicsDecodeEffectCreate(gContext, &audioSettings, &effectSettings, &effect->ambisonicsEffect);

effect->ambisonicsEffectSettingsBackup = effectSettings;
}

if (status == IPL_STATUS_SUCCESS)
Expand All @@ -143,18 +179,29 @@ InitFlags lazyInit(FMOD_DSP_STATE* state,

if (numChannelsIn > 0 && numChannelsOut > 0)
{
int success = IPL_STATUS_SUCCESS;

auto numAmbisonicChannels = numChannelsForOrder(gSimulationSettings.maxOrder);

if (effect->reflectionsBuffer.data && effect->reflectionsBuffer.numChannels != numAmbisonicChannels)
iplAudioBufferFree(gContext, &effect->reflectionsBuffer);

if (!effect->reflectionsBuffer.data)
iplAudioBufferAllocate(gContext, numAmbisonicChannels, audioSettings.frameSize, &effect->reflectionsBuffer);
success |= iplAudioBufferAllocate(gContext, numAmbisonicChannels, audioSettings.frameSize, &effect->reflectionsBuffer);

if (effect->inBuffer.data && effect->inBuffer.numChannels != numChannelsIn)
iplAudioBufferFree(gContext, &effect->inBuffer);

if (!effect->inBuffer.data)
iplAudioBufferAllocate(gContext, numChannelsIn, audioSettings.frameSize, &effect->inBuffer);
success |= iplAudioBufferAllocate(gContext, numChannelsIn, audioSettings.frameSize, &effect->inBuffer);

if (effect->outBuffer.data && effect->outBuffer.numChannels != numChannelsOut)
iplAudioBufferFree(gContext, &effect->outBuffer);

if (!effect->outBuffer.data)
iplAudioBufferAllocate(gContext, numChannelsOut, audioSettings.frameSize, &effect->outBuffer);
success |= iplAudioBufferAllocate(gContext, numChannelsOut, audioSettings.frameSize, &effect->outBuffer);

initFlags = static_cast<InitFlags>(initFlags | INIT_AUDIOBUFFERS);
initFlags = success == IPL_STATUS_SUCCESS ? static_cast<InitFlags>(initFlags | INIT_AUDIOBUFFERS) : initFlags;
}

return initFlags;
Expand All @@ -167,6 +214,7 @@ void reset(FMOD_DSP_STATE* state)
return;

effect->binaural = false;
effect->outputFormat = ParameterSpeakerFormatType::PARAMETER_FROM_MIXER;
}

FMOD_RESULT F_CALL create(FMOD_DSP_STATE* state)
Expand Down Expand Up @@ -212,6 +260,25 @@ FMOD_RESULT F_CALL getBool(FMOD_DSP_STATE* state,
return FMOD_OK;
}

FMOD_RESULT F_CALL getInt(FMOD_DSP_STATE* state,
int index,
int* value,
char*)
{
auto effect = reinterpret_cast<State*>(state->plugindata);

switch (index)
{
case OUTPUT_FORMAT:
*value = static_cast<int>(effect->outputFormat);
break;
default:
return FMOD_ERR_INVALID_PARAM;
}

return FMOD_OK;
}

FMOD_RESULT F_CALL setBool(FMOD_DSP_STATE* state,
int index,
FMOD_BOOL value)
Expand All @@ -230,6 +297,24 @@ FMOD_RESULT F_CALL setBool(FMOD_DSP_STATE* state,
return FMOD_OK;
}

FMOD_RESULT F_CALL setInt(FMOD_DSP_STATE* state,
int index,
int value)
{
auto effect = reinterpret_cast<State*>(state->plugindata);

switch (index)
{
case OUTPUT_FORMAT:
effect->outputFormat = static_cast<ParameterSpeakerFormatType>(value);
break;
default:
return FMOD_ERR_INVALID_PARAM;
}

return FMOD_OK;
}

FMOD_RESULT F_CALL process(FMOD_DSP_STATE* state,
unsigned int length,
const FMOD_DSP_BUFFER_ARRAY* inBuffers,
Expand All @@ -239,6 +324,10 @@ FMOD_RESULT F_CALL process(FMOD_DSP_STATE* state,
{
if (operation == FMOD_DSP_PROCESS_QUERY)
{
auto effect = reinterpret_cast<State*>(state->plugindata);
if (!initFmodOutBufferFormat(inBuffers, outBuffers, state, effect->outputFormat))
return FMOD_ERR_DSP_DONTPROCESS;

if (inputsIdle)
return FMOD_ERR_DSP_DONTPROCESS;
}
Expand Down Expand Up @@ -284,7 +373,7 @@ FMOD_RESULT F_CALL process(FMOD_DSP_STATE* state,
ambisonicsParams.order = gSimulationSettings.maxOrder;
ambisonicsParams.hrtf = gHRTF[0];
ambisonicsParams.orientation = listenerCoordinates;
ambisonicsParams.binaural = (effect->binaural) ? IPL_TRUE : IPL_FALSE;
ambisonicsParams.binaural = numChannelsOut == 2 && (effect->binaural) ? IPL_TRUE : IPL_FALSE;

iplAmbisonicsDecodeEffectApply(effect->ambisonicsEffect, &ambisonicsParams, &effect->reflectionsBuffer, &effect->outBuffer);

Expand Down Expand Up @@ -318,11 +407,11 @@ FMOD_DSP_DESCRIPTION gMixerReturnEffect
MixerReturnEffect::NUM_PARAMS,
MixerReturnEffect::gParamsArray,
nullptr,
nullptr,
MixerReturnEffect::setInt,
MixerReturnEffect::setBool,
nullptr,
nullptr,
nullptr,
MixerReturnEffect::getInt,
MixerReturnEffect::getBool,
nullptr,
nullptr,
Expand Down
21 changes: 17 additions & 4 deletions fmod/src/phonon_fmod.plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ studio.plugins.registerPluginDescription("Steam Audio Spatializer", {
"ReflMixLevel": {displayName: "Reflections Mix Level"},
"PathBinaural": {displayName: "Apply HRTF To Pathing"},
"PathMixLevel": {displayName: "Pathing Mix Level"},
"OutputFormat": {displayName: "Output Format"},
},
deckUi: {
deckWidgetType: studio.ui.deckWidgetType.Layout,
Expand Down Expand Up @@ -253,7 +254,7 @@ studio.plugins.registerPluginDescription("Steam Audio Spatializer", {
deckWidgetType: studio.ui.deckWidgetType.Button,
binding: "ReflBinaural",
text: "On"
}
},
]
},
{
Expand All @@ -275,7 +276,11 @@ studio.plugins.registerPluginDescription("Steam Audio Spatializer", {
deckWidgetType: studio.ui.deckWidgetType.Button,
binding: "PathBinaural",
text: "On"
}
},
{
deckWidgetType: studio.ui.deckWidgetType.Dropdown,
binding: "OutputFormat"
},
]
},
{
Expand Down Expand Up @@ -337,7 +342,11 @@ studio.plugins.registerPluginDescription("Steam Audio Mixer Return", {
binding: "Binaural",
text: "On",
buttonWidth: 64
}
},
{
deckWidgetType: studio.ui.deckWidgetType.Dropdown,
binding: "OutputFormat"
},
]
}
]
Expand Down Expand Up @@ -366,7 +375,11 @@ studio.plugins.registerPluginDescription("Steam Audio Reverb", {
binding: "Binaural",
text: "Enable",
buttonWidth: 64
}
},
{
deckWidgetType: studio.ui.deckWidgetType.Dropdown,
binding: "OutputFormat"
},
]
}
]
Expand Down
Loading

0 comments on commit 4d76d72

Please sign in to comment.