FNA-XNA / FAudio

FAudio - Accuracy-focused XAudio reimplementation for open platforms
https://fna-xna.github.io/
Other
545 stars 73 forks source link

F3DAUDIO_CALCULATE_REDIRECT_TO_LFE issue #126

Closed morffiy closed 5 years ago

morffiy commented 5 years ago

https://github.com/FNA-XNA/FAudio/blob/f7a0ce7e84933ea4913ca130d74cd39e5c080259/src/F3DAudio.c#L1127

The ComputeEmitterChannelCoefficients function does not take into acount LFEDistanceCurve. Maybe this line should look like: pMatrixCoefficients[ curConfig->LFSpeakerIdx * numSrcChannels + currentChannel ] += LFEattenuation;

The comment in CalculateMatrix function seems to be true /* TODO: this could be skipped if the destination has no LFE */ :+1: It really skipped for none multi-channel emitters..

flibitijibibo commented 5 years ago

Send a patch with a test to verify this and we should be all set. I generally don’t touch this file without a very specific test to compare with.

morffiy commented 5 years ago

@flibitijibibo 1.) Set all DSPSetting in F3DAUDIO_DISTANCE_CURVE_POINT for pLFECurve in F3DAUDIO_EMITTER to 0.0f 2.) Run CalculateMatrix with F3DAUDIO_CALCULATE_REDIRECT_TO_LFE.

LFE channel must be silent but it is not due to you redirect pVolumeCurve but not pLFECurve

flibitijibibo commented 5 years ago

Makes sense - we probably just need to pass LFEattenuation into that function and change the line as you suggested. If you can send this in patch form I can merge it along with the other one, provided these fix whatever it is you’re running into.

morffiy commented 5 years ago
@@ -947,6 +947,7 @@ static inline void ComputeEmitterChannelCoefficients(
    float innerRadius,
    F3DAUDIO_VECTOR channelPosition,
    float attenuation,
+        float LFEattenuation,
    uint32_t flags,
    uint32_t currentChannel,
    uint32_t numSrcChannels,
@@ -1124,7 +1125,7 @@ static inline void ComputeEmitterChannelCoefficients(
    if (flags & F3DAUDIO_CALCULATE_REDIRECT_TO_LFE)
    {
        FAudio_assert(curConfig->LFSpeakerIdx != -1);
-       pMatrixCoefficients[curConfig->LFSpeakerIdx * numSrcChannels + currentChannel] += attenuation;
+       pMatrixCoefficients[curConfig->LFSpeakerIdx * numSrcChannels + currentChannel] += LFEattenuation;
    }
 }

@@ -1288,6 +1289,7 @@ static inline void CalculateMatrix(
                pEmitter->InnerRadius,
                listenerToEmChannel,
                attenuation,
+                                LFEattenuation,
                Flags,
                0 /* currentChannel */,
                1 /* numSrcChannels */,
@@ -1330,6 +1332,7 @@ static inline void CalculateMatrix(
                        pEmitter->InnerRadius,
                        listenerToEmChannel,
                        attenuation,
+                                                0.0f,
                        Flags,
                        iEC,
                        pEmitter->ChannelCount,
flibitijibibo commented 5 years ago

Should we be using LFEattenuation instead of 0.0f for multi-channel emitters?

morffiy commented 5 years ago

LFEattenuation is used by multi-channel emitters: https://github.com/FNA-XNA/FAudio/blob/5f27fa295d4f54c2aa22a7f2055dd10511db4d29/src/F3DAudio.c#L1310 IMHO it would be better to get rid of this "if" and pass LFEattenuation to ComputeEmitterChannelCoefficients instead of 0.0f

flibitijibibo commented 5 years ago

I'm thinking the same thing - if we can do exactly that and have the math come out right in the official 3D sample (EDIT: Maybe not, I don't think this has a multi-channel emitter sample)...

https://github.com/walbourn/directx-sdk-samples/tree/master/XAudio2/XAudio2Sound3D https://github.com/SveSop/wine_patches/blob/master/xaudio.tar.xz

... then that's probably the best way to fix this.

flibitijibibo commented 5 years ago

Remembered @lamarqua's test case for this: https://github.com/lamarqua/F3DAudio_tool/blob/master/smalltool/smalltool.cpp#L827

flibitijibibo commented 5 years ago

Okay, so it turns out it's really weird. Here's a test program:

#include <stdio.h>
#include <F3DAudio.h>

int main(int argc, char **argv)
{
    F3DAUDIO_HANDLE instance;
    F3DAUDIO_LISTENER listener;
    F3DAUDIO_EMITTER emitter;
    F3DAUDIO_DSP_SETTINGS dsp;
    float matrix[12];
    float kChannelAzimuths2[] = { 0.0f, F3DAUDIO_PI / 2.0f, F3DAUDIO_PI, F3DAUDIO_PI / 4.0f };
    F3DAUDIO_DISTANCE_CURVE_POINT volPt[] = { { 0.0f, 0.5f }, { 1.0f, 0.5f } };
    F3DAUDIO_DISTANCE_CURVE_POINT lfePt[] = { { 0.0f, 0.3f }, { 1.0f, 0.3f } };
    F3DAUDIO_DISTANCE_CURVE volume = { volPt, 2 };
    F3DAUDIO_DISTANCE_CURVE lfe = { lfePt, 2 };

    listener.OrientFront.x = 0.0f;
    listener.OrientFront.y = -1.0f;
    listener.OrientFront.z = 0.0f;
    listener.OrientTop.x = 1.0f;
    listener.OrientTop.y = 0.0f;
    listener.OrientTop.z = 0.0f;
    listener.Position.x = 0.0f;
    listener.Position.y = 0.0f;
    listener.Position.z = 0.0f;
    listener.Velocity.x = 0.0f;
    listener.Velocity.y = 0.0f;
    listener.Velocity.z = 0.0f;
    listener.pCone = NULL;

    emitter.pCone = NULL;
    emitter.OrientFront.x = 0.0f;
    emitter.OrientFront.y = 1.0f;
    emitter.OrientFront.z = 0.0f;
    emitter.OrientTop.x = 1.0f;
    emitter.OrientTop.y = 0.0f;
    emitter.OrientTop.z = 0.0f;
    emitter.Position.x = 0.0f;
    emitter.Position.y = -1.0f;
    emitter.Position.z = 0.0f;
    emitter.Velocity.x = 0.0f;
    emitter.Velocity.y = 0.0f;
    emitter.Velocity.z = 0.0f;
    emitter.InnerRadius = 0.0f;
    emitter.InnerRadiusAngle = 0.0f;
    emitter.ChannelCount = 4;
    emitter.ChannelRadius = 0.0f;
    emitter.pChannelAzimuths = kChannelAzimuths2;
    emitter.pVolumeCurve = &volume;
    emitter.pLFECurve = &lfe;
    emitter.pLPFDirectCurve = NULL;
    emitter.pLPFReverbCurve = NULL;
    emitter.pReverbCurve = NULL;
    emitter.CurveDistanceScaler = 1.0f;
    emitter.DopplerScaler = 1.0f;

    dsp.pMatrixCoefficients = matrix;
    dsp.pDelayTimes = NULL;
    dsp.SrcChannelCount = 4;
    dsp.DstChannelCount = 3;

    F3DAudioInitialize(SPEAKER_2POINT1, 343.1f, instance);
    F3DAudioCalculate(instance, &listener, &emitter, F3DAUDIO_CALCULATE_MATRIX | F3DAUDIO_CALCULATE_REDIRECT_TO_LFE, &dsp);
    printf("%f\t%f\t%f\n%f\t%f\t%f\n%f\t%f\t%f\n%f\t%f\t%f\n",
    dsp.pMatrixCoefficients[0], dsp.pMatrixCoefficients[1], dsp.pMatrixCoefficients[2],
    dsp.pMatrixCoefficients[3], dsp.pMatrixCoefficients[4], dsp.pMatrixCoefficients[5],
    dsp.pMatrixCoefficients[6], dsp.pMatrixCoefficients[7], dsp.pMatrixCoefficients[8],
    dsp.pMatrixCoefficients[9], dsp.pMatrixCoefficients[10], dsp.pMatrixCoefficients[11]);
    return 0;
}

It turns out that you not only have to pass in LFEattenuation even in that scenario, but you actually have to divide the value by the source channel count! I tried this with various counts and curve values and this definitely seems to be what it does:

https://github.com/FNA-XNA/FAudio/commit/998e914985a40d17811e0da28248cc8b624ef4c4

This was a weird one, thanks for the report!

morffiy commented 5 years ago

@flibitijibibo Are sure about this ?

if (emChAzimuth == F3DAUDIO_2PI)
{
   MatrixCoefficients[curConfig->LFSpeakerIdx * pEmitter->ChannelCount + iEC] = LFEattenuation;
}
flibitijibibo commented 5 years ago

Turns out that actually doesn't intersect with REDIRECT; if you have an LFE channel in your emitter with that flag set it's an invalid call entirely:

Apply an equal mix of all source channels to a low frequency effect (LFE) destination channel. Only applies to matrix calculations with a source that does not have an LFE channel and a destination that does have an LFE channel. This flag is only valid if X3DAUDIO_CALCULATE_MATRIX is also set.

morffiy commented 5 years ago

@flibitijibibo right, just slipped out of my mind.

flibitijibibo commented 5 years ago

Same, I actually ran into the assertion failure myself by accident :P

morffiy commented 5 years ago

@flibitijibibo

https://github.com/FNA-XNA/FAudio/blob/998e914985a40d17811e0da28248cc8b624ef4c4/src/FAudio.c#L2279

BTW there is a bug in xaudio in GetState method.

voice->SubmitSourceBuffer( &buffer )
voice->Start()
... let it play a bit
voice->Stop()
voice->FlushSourceBuffers()

voice->SubmitSourceBuffer( &buffer )
voice->Start()
XAUDIO2_VOICE_STATE state;
voice->GetState( &state );
ASSERT( state.SamplesPlayed == 0 )
voice->Stop()
voice->FlushSourceBuffers()

if you loop this code the state.SamplesPlayed will never be zero again it will grow to infinity

morffiy commented 5 years ago

@flibitijibibo Regarding this

* Note: It is SUSPICIOUSLY close to 1/sqrt(2), but I haven't figured out why.

and this

/* Lerping here is an approximation.
* TODO: High accuracy version. Having stared at the curves long
* enough, I'm pretty sure this is a quadratic, but trying to
* polyfit with numpy didn't give nice, round polynomial
* coefficients...
* -Adrien
*/

1/sqrt(2) = -3db = 45 degrees

i think this

ms = LERP(2.0f * normalizedRadialDist, 0.0f, DIFFUSION_LERP_MIDPOINT_VALUE);

should be

float DBToLinear( float db ) { 
    return powf( 2.0f, db * ( 1.0f / 6.0f ) ); 
  }
ms = DBToLinear( LERP(2.0f * normalizedRadialDist, -96, -3 ) );

and this

ms = LERP(2.0f * (normalizedRadialDist - 0.5f), DIFFUSION_LERP_MIDPOINT_VALUE, 1.0f);

should be

float DBToLinear( float db ) { 
    return powf( 2.0f, db * ( 1.0f / 6.0f ) ); 
  }
ms = DBToLinear( LERP(2.0f * (normalizedRadialDist - 0.5f), -3, 0.0f) );

BR Tima

flibitijibibo commented 5 years ago

Can you run the 3D idea against @lamarqua’s tests?

https://github.com/lamarqua/F3DAudio_tool

The SamplesPlayed idea should be added to this test program:

https://github.com/FNA-XNA/FAudio/blob/master/tests/xaudio2.c

flibitijibibo commented 5 years ago

Your GetState report appears to be invalid:

https://github.com/FNA-XNA/FAudio/commit/f9dd17926d30fee149430f30f082c00a4d19bcf3 EDIT: https://github.com/FNA-XNA/FAudio/commit/49461af86d0f67b0ee0c0b4d18f01d00120f456c

flibitijibibo commented 5 years ago

Wait, so the bug is on your end...? It sounds like the assert is deliberately wrong.

morffiy commented 5 years ago

Why deliberately wrong ? After the voice is started the SamplesPlayed should be ~0 but in my case it points deep inside, ~somewhere it was stopped before it was playing.

flibitijibibo commented 5 years ago

AFAIK that's the official behavior - the specification suggests that stopping the voice will reset SamplesPlayed but it looks like stopping and even flushing the buffers doesn't do this. The only way to reset the value is either to submit a buffer with END_OF_STREAM or call Discontinuity(), which sets the flag after submitting the buffer.

morffiy commented 5 years ago

No. either END_OF_STREAM or call Discontinuity() won't help, i double checked it.

flibitijibibo commented 5 years ago

You'll most likely have to wait for the buffer to completely finish - you'll get an OnStreamEnd event:

https://docs.microsoft.com/en-us/windows/desktop/api/xaudio2/nf-xaudio2-ixaudio2voicecallback-onstreamend

morffiy commented 5 years ago

No. i did check it too. I waited ~1 minute before starting the voice which has been stopped and it still keeps on fire the assert.

flibitijibibo commented 5 years ago

Very well - you will need to write the unit test yourself, I cannot verify this on my end and I can't spend any more time on it.

lamarqua commented 5 years ago
/* Lerping here is an approximation.
* TODO: High accuracy version. Having stared at the curves long
* enough, I'm pretty sure this is a quadratic, but trying to
* polyfit with numpy didn't give nice, round polynomial
* coefficients...
* -Adrien
*/

1/sqrt(2) = -3db = 45 degrees

i think this

ms = LERP(2.0f * normalizedRadialDist, 0.0f, DIFFUSION_LERP_MIDPOINT_VALUE);

should be

float DBToLinear( float db ) { 
    return powf( 2.0f, db * ( 1.0f / 6.0f ) ); 
  }
ms = DBToLinear( LERP(2.0f * normalizedRadialDist, -96, -3 ) );

and this

ms = LERP(2.0f * (normalizedRadialDist - 0.5f), DIFFUSION_LERP_MIDPOINT_VALUE, 1.0f);

should be

float DBToLinear( float db ) { 
    return powf( 2.0f, db * ( 1.0f / 6.0f ) ); 
  }
ms = DBToLinear( LERP(2.0f * (normalizedRadialDist - 0.5f), -3, 0.0f) );

I don't quite remember all the minutiae about this but I think you might be onto something. I recently read a paper on spatialization and it seems that there are a whole bunch of methods to preserve constant power (rather than amplitude). See this article for example, section 1 ("Law of tangents", maybe this is equivalent to what you're doing but I can't do the formulation / calculation right now). I was going to return to this and fix it eventually but sounds like you are already on it :)