// 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 GetUnrealOrder() { static ESpeakerShortNames Order[] { #define CSV(X) X, FOREACH_ENUM_ESPEAKERSHORTNAMES(CSV) #undef CSV }; return Order; } static void TranslateGainMatrix( const TArrayView& InGainMatrix, const int32 InNumInputChannels, const int32 InNumOutputChannels, const TArrayView& InOldOrder, const TArrayView& InNewOrder, TArray& OutGains) { // Channels not found in new order will be zeroed. OutGains.SetNumZeroed(InNumOutputChannels*InNumInputChannels); // Lookup (old->new). int32 Lookup[static_cast(ESpeakerShortNames::NumChannels)] = { INDEX_NONE }; for (int32 i = 0; i < InOldOrder.Num(); ++i) { Lookup[static_cast(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(OldIn)]; NewIn != INDEX_NONE) { for (int32 OutputChannelIndex=0; OutputChannelIndex < InNumOutputChannels; ++OutputChannelIndex) { const ESpeakerShortNames OldOut = InOldOrder[OutputChannelIndex]; if (const int32 NewOut = Lookup[static_cast(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 Src, TArrayView 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 SrcChannels, TArrayView DstChannels, const int32 NumFrames) { // Copy everything destination wants, and nothing else. const TArray& SrcOrder = InSrcType.GetSpeakerOrder(); const TArray& 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 Gains; Create2DChannelMap( { .NumInputChannels = InSrcType.NumChannels(), .NumOutputChannels = InDstType.NumChannels(), .Order = EChannelMapOrder::OutputMajorOrder, .MonoUpmixMethod = InParams.MixMethod, .bIsCenterChannelOnly = InSrcType.NumChannels() == 1 && InSrcType.HasSpeaker(ESpeakerShortNames::FC) }, Gains)) { TArray TranslatedGains; TArray 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 InSrc, TArrayView 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& Speakers = InFromType.GetSpeakerOrder(); TArray> AllChannelGainArrays; AllChannelGainArrays.SetNum(InFromType.NumChannels()); for (int32 i = 0; i < InFromType.NumChannels(); ++i) { const FDiscreteChannelTypeFamily::FSpeaker& Speaker = Speakers[i]; TArray& 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 Src, TArrayView 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> AllChannelGainArrays; AllChannelGainArrays.SetNum(InToType.NumChannels()); const TArray& Order = InToType.GetSpeakerOrder(); for (int32 i = 0; i < InToType.NumChannels(); ++i) { const FDiscreteChannelTypeFamily::FSpeaker& Speaker = Order[i]; TArray& 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 Src, TArrayView Dst, const int32 NumFrames) { for (int32 i = 0; i < NumSpeakers; ++i) { TArrayView 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 InSrc, TArrayView 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 InSrc, TArrayView 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); } }; } }