Skip to content

Commit

Permalink
feat(hrtf): Configure AMIR asset and sampling mode from engine configs.
Browse files Browse the repository at this point in the history
Signed-off-by: Axel Nana <[email protected]>
  • Loading branch information
na2axl committed Oct 3, 2024
1 parent 924eeee commit 88a08ad
Show file tree
Hide file tree
Showing 14 changed files with 197 additions and 59 deletions.
Binary file added assets/data/sadie_h12.amir
Binary file not shown.
20 changes: 20 additions & 0 deletions include/SparkyStudios/Audio/Amplitude/Core/Common.h
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,26 @@ namespace SparkyStudios::Audio::Amplitude
ePanningMode_BinauralHighQuality = 3,
};

/**
* @brief Defines how the HRIR sphere is sampled when doing Ambisonics binauralization.
*
* @ingroup core
*/
enum eHRIRSphereSamplingMode : AmUInt8
{
/**
* @brief Provides the most accurate binauralization, as the HRIR data are smoothly transitioned between sphere points.
*
* See more info about bilinear sampling [here](http://www02.smt.ufrj.br/~diniz/conf/confi117.pdf).
*/
eHRIRSphereSamplingMode_Bilinear = 0,

/**
* @brief Provides a more efficient binauralization, as the HRIR data are interpolated using only the nearest neighbors.
*/
eHRIRSphereSamplingMode_NearestNeighbor = 1,
};

/**
* @brief Describe the format of an audio sample.
*
Expand Down
25 changes: 23 additions & 2 deletions include/SparkyStudios/Audio/Amplitude/Core/Engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
#include <SparkyStudios/Audio/Amplitude/Core/Room.h>
#include <SparkyStudios/Audio/Amplitude/Core/Version.h>

#include <SparkyStudios/Audio/Amplitude/HRTF/HRIRSphere.h>

#include <SparkyStudios/Audio/Amplitude/Mixer/Amplimix.h>
#include <SparkyStudios/Audio/Amplitude/Mixer/Pipeline.h>

Expand Down Expand Up @@ -75,7 +77,7 @@ namespace SparkyStudios::Audio::Amplitude
public:
virtual ~Engine() = default;

#pragma region Miscalaneous
#pragma region Miscellaneous

/**
* @brief Gets the version structure.
Expand Down Expand Up @@ -1215,7 +1217,7 @@ namespace SparkyStudios::Audio::Amplitude

#pragma endregion

#pragma region Engine State& Configuration
#pragma region Engine State and Configuration

/**
* @brief Get the current speed of sound.
Expand Down Expand Up @@ -1296,6 +1298,25 @@ namespace SparkyStudios::Audio::Amplitude
*/
[[nodiscard]] virtual ePanningMode GetPanningMode() const = 0;

/**
* Gets the HRIR sphere sampling mode defined in the loaded engine configuration.
*
* @return The HRIR sphere sampling mode.
*/
[[nodiscard]] virtual eHRIRSphereSamplingMode GetHRIRSphereSamplingMode() const = 0;

/**
* Gets the HRIR sphere defined in the loaded engine configuration.
*
* @return The HRIR sphere. If no HRIR sphere is defined, returns nullptr.
*
* @note The HRIR sphere is optional and can be null in some cases. If the
* engine does not have an HRIR sphere defined, this function will return `nullptr`.
*
* @see HRIRSphere
*/
[[nodiscard]] virtual const HRIRSphere* GetHRIRSphere() const = 0;

#pragma endregion

#pragma region Plugins Management
Expand Down
19 changes: 10 additions & 9 deletions include/SparkyStudios/Audio/Amplitude/HRTF/HRIRSphere.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,24 +149,25 @@ namespace SparkyStudios::Audio::Amplitude
[[nodiscard]] virtual AmUInt32 GetIRLength() const = 0;

/**
* @brief Samples the HRIR sphere for the given direction using bilinear interpolation.
* @brief Sets the sampling mode for the HRIR sphere.
*
* See more info about bilinear sampling [here](http://www02.smt.ufrj.br/~diniz/conf/confi117.pdf).
*
* @param[in] direction The sound to listener direction.
* @param[out] leftHRIR The left HRIR data.
* @param[out] rightHRIR The right HRIR data.
* @param[in] mode The sampling mode to use.
*/
virtual void SetSamplingMode(eHRIRSphereSamplingMode mode) = 0;

/**
* @brief Gets the sampling mode for the HRIR sphere.
*/
virtual void SampleBilinear(const AmVec3& direction, AmReal32* leftHRIR, AmReal32* rightHRIR) const = 0;
[[nodiscard]] virtual eHRIRSphereSamplingMode GetSamplingMode() const = 0;

/**
* @brief Samples the HRIR sphere for the given direction using nearest neighbor interpolation.
* @brief Samples the HRIR sphere for the given direction.
*
* @param[in] direction The sound to listener direction.
* @param[out] leftHRIR The left HRIR data.
* @param[out] rightHRIR The right HRIR data.
*/
virtual void SampleNearestNeighbor(const AmVec3& direction, AmReal32* leftHRIR, AmReal32* rightHRIR) const = 0;
virtual void Sample(const AmVec3& direction, AmReal32* leftHRIR, AmReal32* rightHRIR) const = 0;

virtual void Transform(const AmMat4& matrix) = 0;

Expand Down
6 changes: 5 additions & 1 deletion samples/rawassets/pc.config.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"driver": "miniaudio",
"output": {
"frequency": 44100,
"frequency": 48000,
"buffer_size": 4096,
"format": "Float32"
},
Expand All @@ -11,6 +11,10 @@
"virtual_channels": 100,
"pipeline": "default.ampipeline"
},
"hrtf": {
"amir_file": "data/sadie_h12.amir",
"hrir_sampling": "Bilinear"
},
"game": {
"listener_fetch_mode": "Nearest",
"track_environments": true,
Expand Down
37 changes: 31 additions & 6 deletions schemas/engine_config_definition.fbs
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ enum eListenerFetchMode : ushort {
/// that only one listener is used everytime.
///
/// The default listener should be set using Engine::SetDefaultListener().
/// If no listener is set to default, the behavior is the same as ListenerFetchMode::None.
/// If no listener is set to default, the behavior is the same as eListenerFetchMode::None.
/// If the listener set as default is next removed, the behavior will be the same as
/// ListenerFetchMode::None, until another default listener is set.
/// eListenerFetchMode::None, until another default listener is set.
Default,

/// Fetches for the first available listener. This ensures
Expand All @@ -67,6 +67,18 @@ enum eListenerFetchMode : ushort {
Last,
}

/// Defines how the HRIR sphere is sampled when doing Ambisonics
/// binauralization.
enum HRIRSphereSamplingMode : ushort {
/// Provides the most accurate binauralization, as the HRIR data are smoothly
/// transitioned between sphere points.
Bilinear,

/// Provides the fastest binauralization, as the closest sphere point to the
/// current direction is always picked.
NearestNeighbor,
}

/// Playback device configuration
table PlaybackOutputConfig {
/// Output sampling frequency in samples per second.
Expand All @@ -91,9 +103,9 @@ table AudioMixerConfig {
/// still tracked.
virtual_channels:uint;

/// Defines the panning mode to use. If Stereo mode is set, all the sound sources
/// playing with HRTF spatialization will fallback to PositionOrientation spatialization.
panning_mode:PanningMode;
/// Defines the panning mode to use. Using any valy other than Stereo
/// will apply Ambisonics binauralization with the defined HRIR sphere file.
panning_mode:PanningMode = Stereo;

/// The name of the pipeline asset file to load.
pipeline:string (required);
Expand Down Expand Up @@ -129,7 +141,7 @@ table GameSyncConfig {
rooms:uint = 1024;

/// The maximum speed of sound in the game.
sound_speed:float = 333.0;
sound_speed:float = 343.0;

/// The Doppler factor. Set 1.0 to normal Doppler effect,
/// set 0.0 to disable Doppler effect.
Expand All @@ -148,13 +160,26 @@ table GameSyncConfig {
track_environments:bool = true;
}

/// HRTF and Ambisonics binauralization configuration
table HRTFConfig {
/// The path to the AMIR asset file (generated using the amit tool).
amir_file:string (required);

/// The HRIR sampling mode.
hrir_sampling:
HRIRSphereSamplingMode = NearestNeighbor;
}

table EngineConfigDefinition {
/// Configures the playback device.
output:PlaybackOutputConfig (required);

/// Configures the audio mixer.
mixer:AudioMixerConfig (required);

/// Configures HRTF processing.
hrtf:HRTFConfig;

/// Configures the game sync.
game:GameSyncConfig (required);

Expand Down
10 changes: 5 additions & 5 deletions src/Ambisonics/AmbisonicBinauralizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ namespace SparkyStudios::Audio::Amplitude
hrir[0].Clear();
hrir[1].Clear();

_hrir->SampleBilinear(position.ToCartesian(), hrir[0].GetBuffer(), hrir[1].GetBuffer());
_hrir->Sample(position.ToCartesian(), hrir[0].GetBuffer(), hrir[1].GetBuffer());

// Scale the HRTFs by the coefficient of the current channel/component
// The spherical harmonic coefficients are multiplied by (2*order + 1) to provide the correct decoder
// for SN3D normalized Ambisonic inputs.
// The spherical harmonic coefficients are multiplied by (2*order + 1) to provide the correct decoder
// for SN3D normalized Ambisonic inputs.
const AmReal32 coefficient =
_decoder.GetSpeakerCoefficient(i, c) * (2.f * std::floor(std::sqrt(static_cast<AmReal32>(c))) + 1.f);

Expand Down Expand Up @@ -111,8 +111,8 @@ namespace SparkyStudios::Audio::Amplitude

for (AmUInt32 c = 0; c < m_channelCount; c++)
{
_convL[c].Init(kInterpolationBlockSize, _accumulatedHRIR[0][c].begin(), _hrir->GetIRLength());
_convR[c].Init(kInterpolationBlockSize, _accumulatedHRIR[1][c].begin(), _hrir->GetIRLength());
_convL[c].Init(_hrir->GetIRLength(), _accumulatedHRIR[0][c].begin(), _hrir->GetIRLength());
_convR[c].Init(_hrir->GetIRLength(), _accumulatedHRIR[1][c].begin(), _hrir->GetIRLength());
}

return true;
Expand Down
40 changes: 39 additions & 1 deletion src/Core/Engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -688,9 +688,30 @@ namespace SparkyStudios::Audio::Amplitude
return false;
}

// Load the panning mode
// Store the panning mode
_state->panning_mode = static_cast<ePanningMode>(config->mixer()->panning_mode());

const auto* hrtfConfig = config->hrtf();

if (hrtfConfig != nullptr)
{
// Store the HRIR sampling mode
_state->hrir_sampling_mode = static_cast<eHRIRSphereSamplingMode>(config->hrtf()->hrir_sampling());

// Load the HRIR sphere
_state->hrir_sphere = ampoolnew(MemoryPoolKind::Engine, HRIRSphereImpl);
_state->hrir_sphere->SetResource(AM_STRING_TO_OS_STRING(config->hrtf()->amir_file()->c_str()));
_state->hrir_sphere->SetSamplingMode(_state->hrir_sampling_mode);
_state->hrir_sphere->Load(GetFileSystem());
}
else if (_state->panning_mode != ePanningMode_Stereo)
{
amLogCritical("The HRTF configuration is missing, but the panning mode is not stereo. Please provide an HRTF configuration, or "
"set the panning mode to Stereo.");
Deinitialize();
return false;
}

// Initialize audio mixer
if (!_state->mixer.Init(config))
{
Expand Down Expand Up @@ -819,6 +840,13 @@ namespace SparkyStudios::Audio::Amplitude
// Unload sound banks
UnloadSoundBanks();

// Release HRIR sphere
if (_state->hrir_sphere != nullptr)
{
ampooldelete(MemoryPoolKind::Engine, HRIRSphereImpl, _state->hrir_sphere);
_state->hrir_sphere = nullptr;
}

ampooldelete(MemoryPoolKind::Engine, EngineInternalState, _state);
_state = nullptr;

Expand Down Expand Up @@ -2359,6 +2387,16 @@ namespace SparkyStudios::Audio::Amplitude
return _state->panning_mode;
}

eHRIRSphereSamplingMode EngineImpl::GetHRIRSphereSamplingMode() const
{
return _state->hrir_sampling_mode;
}

const HRIRSphere* EngineImpl::GetHRIRSphere() const
{
return _state->hrir_sphere;
}

#pragma endregion

Channel EngineImpl::PlayScopedSwitchContainer(
Expand Down
2 changes: 2 additions & 0 deletions src/Core/Engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ namespace SparkyStudios::Audio::Amplitude
[[nodiscard]] const Curve& GetObstructionCoefficientCurve() const override;
[[nodiscard]] const Curve& GetObstructionGainCurve() const override;
[[nodiscard]] ePanningMode GetPanningMode() const override;
[[nodiscard]] eHRIRSphereSamplingMode GetHRIRSphereSamplingMode() const override;
[[nodiscard]] const HRIRSphere* GetHRIRSphere() const override;

private:
Channel PlayScopedSwitchContainer(
Expand Down
15 changes: 11 additions & 4 deletions src/Core/EngineInternalState.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <Core/RoomInternalState.h>

#include <Core/Event.h>
#include <HRTF/HRIRSphere.h>
#include <Mixer/Amplimix.h>
#include <Mixer/Pipeline.h>
#include <Sound/Attenuation.h>
Expand Down Expand Up @@ -154,7 +155,7 @@ namespace SparkyStudios::Audio::Amplitude
, current_frame(0)
, total_time(0.0)
, listener_fetch_mode(eListenerFetchMode_None)
, sound_speed(333.0)
, sound_speed(343.0)
, doppler_factor(1.0)
, obstruction_config()
, occlusion_config()
Expand All @@ -163,13 +164,15 @@ namespace SparkyStudios::Audio::Amplitude
, track_environments(false)
, samples_per_stream(512)
, panning_mode(ePanningMode_Stereo)
, hrir_sampling_mode(eHRIRSphereSamplingMode_NearestNeighbor)
, hrir_sphere(nullptr)
, version(nullptr)
{}

AmplimixImpl mixer;

// Hold the audio buses definition file contents.
std::string buses_source;
AmString buses_source;

// The state of the buses.
std::vector<BusInternalState> buses;
Expand Down Expand Up @@ -250,7 +253,7 @@ namespace SparkyStudios::Audio::Amplitude
PipelineImpl pipeline;

// Hold the audio buses definition file contents.
std::string pipeline_source;
AmString pipeline_source;

// The pre-allocated pool of all ChannelInternalState objects
ChannelStateVector channel_state_memory;
Expand Down Expand Up @@ -303,6 +306,10 @@ namespace SparkyStudios::Audio::Amplitude

ePanningMode panning_mode;

eHRIRSphereSamplingMode hrir_sampling_mode;

HRIRSphereImpl* hrir_sphere;

const struct Version* version;
};

Expand Down Expand Up @@ -341,7 +348,7 @@ namespace SparkyStudios::Audio::Amplitude
// listener, respectively.
AmVec2 CalculatePan(const AmVec3& listenerSpaceLocation);

bool LoadFile(const std::shared_ptr<File>& file, std::string* dest);
bool LoadFile(const std::shared_ptr<File>& file, AmString* dest);

AmUInt32 GetMaxNumberOfChannels(const EngineConfigDefinition* config);
} // namespace SparkyStudios::Audio::Amplitude
Expand Down
Loading

0 comments on commit 88a08ad

Please sign in to comment.