Files
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

238 lines
9.0 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ChannelAgnostic/ChannelAgnosticTranscoding.h"
#include "Algo/Transform.h"
#include "DSP/Ambisonics.h"
#include "DSP/MultiMono.h"
#include "DSP/SphericalHarmonicCalculator.h"
#include "TypeFamily/ChannelTypeFamily.h"
namespace Audio
{
static TArrayView<ESpeakerShortNames> GetUnrealOrder()
{
static ESpeakerShortNames Order[]
{
#define CSV(X) X,
FOREACH_ENUM_ESPEAKERSHORTNAMES(CSV)
#undef CSV
};
return Order;
}
static void TranslateGainMatrix(
const TArrayView<float>& InGainMatrix,
const int32 InNumInputChannels,
const int32 InNumOutputChannels,
const TArrayView<const ESpeakerShortNames>& InOldOrder,
const TArrayView<const ESpeakerShortNames>& InNewOrder,
TArray<float>& OutGains)
{
// Channels not found in new order will be zeroed.
OutGains.SetNumZeroed(InNumOutputChannels*InNumInputChannels);
// Lookup (old->new).
int32 Lookup[static_cast<int32>(ESpeakerShortNames::NumChannels)] = { INDEX_NONE };
for (int32 i = 0; i < InOldOrder.Num(); ++i)
{
Lookup[static_cast<int32>(InOldOrder[i])] = InNewOrder.IndexOfByKey(InOldOrder[i]);
}
// Translate.
for (int32 InputChannelIndex=0; InputChannelIndex < InNumInputChannels; ++InputChannelIndex)
{
// This input channel has a mapping in the new?
const ESpeakerShortNames OldIn = InOldOrder[InputChannelIndex];
if (const int32 NewIn = Lookup[static_cast<int32>(OldIn)]; NewIn != INDEX_NONE)
{
for (int32 OutputChannelIndex=0; OutputChannelIndex < InNumOutputChannels; ++OutputChannelIndex)
{
const ESpeakerShortNames OldOut = InOldOrder[OutputChannelIndex];
if (const int32 NewOut = Lookup[static_cast<int32>(OldOut)]; NewOut != INDEX_NONE)
{
OutGains[(NewIn * InNumOutputChannels) + NewOut] =
InGainMatrix[(InputChannelIndex * InNumOutputChannels) + OutputChannelIndex];
}
}
}
}
}
// Discrete to Discrete
FChannelTypeFamily::FTranscoder GetTranscoder(
const FDiscreteChannelTypeFamily& InSrcType, const FDiscreteChannelTypeFamily& InDstType, const FChannelTypeFamily::FGetTranscoderParams& InParams)
{
// Exact match? We can just memcpy each channel.
// TODO. in future these could be shared-ptrs from the main CAT memory block.
if (&InDstType == &InSrcType)
{
const int32 NumChannels = InDstType.NumChannels();
return [NumChannels](TArrayView<const float*> Src, TArrayView<float*> Dst, const int32 NumFrames)
{
for (int32 i = 0; i < NumChannels; ++i)
{
FMemory::Memcpy(Dst[i], Src[i], NumFrames * sizeof(float));
}
};
}
switch (InParams.TranscodeMethod)
{
case EChannelTranscodeMethod::ChannelDrop:
{
return [&InSrcType,&InDstType](TArrayView<const float*> SrcChannels, TArrayView<float*> DstChannels, const int32 NumFrames)
{
// Copy everything destination wants, and nothing else.
const TArray<FDiscreteChannelTypeFamily::FSpeaker>& SrcOrder = InSrcType.GetSpeakerOrder();
const TArray<FDiscreteChannelTypeFamily::FSpeaker>& DstOrder = InDstType.GetSpeakerOrder();
const int32 NumDstChannels = InDstType.NumChannels();
for (int32 i = 0; i < NumDstChannels; ++i)
{
const ESpeakerShortNames DstSpeaker = DstOrder[i].Speaker;
if (const int32 SrcChannelIndex = SrcOrder.IndexOfByPredicate([&](const FDiscreteChannelTypeFamily::FSpeaker& j) { return j.Speaker == DstSpeaker; });
SrcChannelIndex != INDEX_NONE)
{
FMemory::Memcpy(DstChannels[i], SrcChannels[SrcChannelIndex], NumFrames * sizeof(float));
}
}
};
}
case EChannelTranscodeMethod::MixUpOrDown:
{
// Make a mix matrix and call a mix/up down.
if (TArray<float> Gains; Create2DChannelMap(
{
.NumInputChannels = InSrcType.NumChannels(),
.NumOutputChannels = InDstType.NumChannels(),
.Order = EChannelMapOrder::OutputMajorOrder,
.MonoUpmixMethod = InParams.MixMethod,
.bIsCenterChannelOnly = InSrcType.NumChannels() == 1 && InSrcType.HasSpeaker(ESpeakerShortNames::FC)
}, Gains))
{
TArray<float> TranslatedGains;
TArray<ESpeakerShortNames> FromOrder;
Algo::Transform(InSrcType.GetSpeakerOrder(), FromOrder, [](const FDiscreteChannelTypeFamily::FSpeaker& i) { return i.Speaker; });
TranslateGainMatrix(Gains, InSrcType.NumChannels(), InDstType.NumChannels(), GetUnrealOrder(), FromOrder, TranslatedGains);
return [MixGains = MoveTemp(TranslatedGains)](TArrayView<const float*> InSrc, TArrayView<float*> InDst, const int32 NumFrames)
{
MultiMonoMixUpOrDown(InSrc, InDst, NumFrames, MixGains);
};
}
}
}
// fail.
return {};
}
// Discrete to Ambisonics
FChannelTypeFamily::FTranscoder GetTranscoder(const FDiscreteChannelTypeFamily& InFromType,
const FAmbisonicsChannelTypeFamily& InToType, const FChannelTypeFamily::FGetTranscoderParams&)
{
const TArray<FDiscreteChannelTypeFamily::FSpeaker>& Speakers = InFromType.GetSpeakerOrder();
TArray<TArray<float>> AllChannelGainArrays;
AllChannelGainArrays.SetNum(InFromType.NumChannels());
for (int32 i = 0; i < InFromType.NumChannels(); ++i)
{
const FDiscreteChannelTypeFamily::FSpeaker& Speaker = Speakers[i];
TArray<float>& GainArray = AllChannelGainArrays[i];
GainArray.SetNumZeroed(InToType.NumChannels());
// Skip LFE.
if (Speakers[i].Speaker == ESpeakerShortNames::LFE)
{
continue;
}
//FVector2f Spherical = Speakers[i].Position.UnitCartesianToSpherical();
//FSphericalHarmonicCalculator::AdjustUESphericalCoordinatesForAmbisonics(Spherical);
const float AzimuthRads = FMath::DegreesToRadians(Speaker.AzimuthDegrees);
const float ElevationRads = FMath::DegreesToRadians(Speaker.ElevationDegrees);
FSphericalHarmonicCalculator::ComputeSoundfieldChannelGains(InToType.GetAmbisonicsOrder(), AzimuthRads, ElevationRads, GainArray);
FSphericalHarmonicCalculator::NormalizeGains(GainArray);
}
return [Gains = MoveTemp(AllChannelGainArrays)](TArrayView<const float*> Src, TArrayView<float*> Dst, const int32 NumFrames)
{
const int32 NumSrcChannels = Src.Num();
for( int32 i = 0; i < Dst.Num(); ++i)
{
FMemory::Memzero(Dst[i], sizeof(float) * NumFrames);
}
for (int32 SourceChannelIndex = 0; SourceChannelIndex < NumSrcChannels; ++SourceChannelIndex)
{
EncodeMonoAmbisonicMixIn(MakeArrayView(Src[SourceChannelIndex], NumFrames), Dst, Gains[SourceChannelIndex]);
}
};
}
// Ambisonics to Discrete
FChannelTypeFamily::FTranscoder GetTranscoder(const FAmbisonicsChannelTypeFamily& InFromType, const FDiscreteChannelTypeFamily& InToType, const FChannelTypeFamily::FGetTranscoderParams&)
{
TArray<TArray<float>> AllChannelGainArrays;
AllChannelGainArrays.SetNum(InToType.NumChannels());
const TArray<FDiscreteChannelTypeFamily::FSpeaker>& Order = InToType.GetSpeakerOrder();
for (int32 i = 0; i < InToType.NumChannels(); ++i)
{
const FDiscreteChannelTypeFamily::FSpeaker& Speaker = Order[i];
TArray<float>& GainArray = AllChannelGainArrays[i];
GainArray.SetNum(InFromType.NumChannels());
// Skip LFE.
if (Order[i].Speaker == ESpeakerShortNames::LFE)
{
continue;
}
//FVector2f Spherical = Order[i].Position.UnitCartesianToSpherical();
//FSphericalHarmonicCalculator::AdjustUESphericalCoordinatesForAmbisonics(Spherical);
check(GainArray.Num() == FAmbisonicsChannelTypeFamily::OrderToNumChannels(InFromType.GetAmbisonicsOrder()));
const float AzimuthRads = FMath::DegreesToRadians(Speaker.AzimuthDegrees);
const float ElevationRads = FMath::DegreesToRadians(Speaker.ElevationDegrees);
FSphericalHarmonicCalculator::ComputeSoundfieldChannelGains(InFromType.GetAmbisonicsOrder(), AzimuthRads, ElevationRads, GainArray);
FSphericalHarmonicCalculator::NormalizeGains(GainArray);
}
return [NumSpeakers = InToType.NumChannels(), Gains = MoveTemp(AllChannelGainArrays)](TArrayView<const float*> Src, TArrayView<float*> Dst, const int32 NumFrames)
{
for (int32 i = 0; i < NumSpeakers; ++i)
{
TArrayView<float> DstChannel = MakeArrayView(Dst[i],NumFrames);
FMemory::Memzero(DstChannel.GetData(), DstChannel.NumBytes()); // Decoder is MixIn.
DecodeMonoAmbisonicMixIn(Src, DstChannel, Gains[i]);
}
};
}
// Ambisonics to Ambisonics
FChannelTypeFamily::FTranscoder GetTranscoder(const FAmbisonicsChannelTypeFamily& InFromType,
const FAmbisonicsChannelTypeFamily& InToType, const FChannelTypeFamily::FGetTranscoderParams& InParams)
{
// Ambisonics to Ambisonics
if (&InToType == &InFromType)
{
// Same, so memcpy.
return [](TArrayView<const float*> InSrc, TArrayView<float*> InDst, const int32 NumFrames)
{
for (int32 i = 0; i < InSrc.Num(); ++i)
{
FMemory::Memcpy(InDst[i], InSrc[i], sizeof(float) * NumFrames);
}
};
}
const int32 NumChannelsToCopy = FMath::Min(InToType.NumChannels(), InFromType.NumChannels());
return [NumChannelsToCopy](TArrayView<const float*> InSrc, TArrayView<float*> InDst, const int32 NumFrames)
{
// Copy as many channels as destination will hold.
for (int32 i = 0; i < NumChannelsToCopy; ++i)
{
FMemory::Memcpy(InDst[i], InSrc[i], sizeof(float) * NumFrames);
}
};
}
}