FNA-XNA / FAudio

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

Rez Infinite asserts in FAudioFXReverb_LockForProcess() #225

Closed mrpippy closed 3 years ago

mrpippy commented 3 years ago

Rez Infinite from Steam asserts in FAudioFXReverb_LockForProcess() soon after startup, it looks like the issue is that input and output channels are both 5.1. I'm not sure if the XAudio2 reverb APO actually supports this (despite claiming not to), or if the channel counts just aren't correct.

Log messages: https://gist.github.com/mrpippy/3edad985fd515c2d9d71e4cc8cac1261

flibitijibibo commented 3 years ago

First guess is that we may need to return an appropriate HRESULT to get working behavior. Otherwise I can only imagine there being a path where the input/output channels match... seems unlikely though, especially 5.1->5.1 which would be really expensive to process.

flibitijibibo commented 3 years ago

My mistake... looks like we already do, but it's a pretty sloppy block:

https://github.com/FNA-XNA/FAudio/blob/890f44e4f74f474524a933e0aca8962c4735e183/src/FAudio.c#L1403-L1415

I suppose we'll have to figure out how to handle this non-destructively (assuming that 5.1->5.1 path doesn't exist... man I hope it doesn't).

flibitijibibo commented 3 years ago

Looks like the source to XAudio2.7 matches the documentation, so the above block is indeed the real issue:

https://github.com/Microsoft/DirectXTK/issues/170#issuecomment-479617599

mrpippy commented 3 years ago

I made a test app that makes the same calls as the game, and the SetEffectChain() call that fails with FAudio succeeds on Windows. I guess that means it actually supports 5.1->5.1?

https://gist.github.com/mrpippy/f2ddbf57bed572c0f056612dbdf8f98f

The game later crashes when it tries to call SetEffectParameters() on the submix voice, but the effect chain never got set.

flibitijibibo commented 3 years ago

Odd, it produces working sound as well? I wonder if there's a path for matching channels.

In any case this moves the issue to reverb, which is less scary but does involve messing with all the arrays to support 6<->6 processing.

mrpippy commented 3 years ago

Yeah, the game produces working sound on Windows (just a laptop with 2 channel output)

flibitijibibo commented 3 years ago

Some quick shortcut links...

I think those are the only spots that need new code, but as with most things I always forget some detail in there.

mrpippy commented 3 years ago

I was able to stub-out the 5.1->5.1 reverb support and the game gets to the menu, I don't have the audio knowledge to take this farther though.

Also the game uses XAPO FXECHO (see the bottom of the test app) which isn't implemented.

diff --git a/src/FAudioFX_reverb.c b/src/FAudioFX_reverb.c
index bd58a06..1fbd60b 100644
--- a/src/FAudioFX_reverb.c
+++ b/src/FAudioFX_reverb.c
@@ -521,7 +521,7 @@ static inline void DspReverb_Create(
 ) {
    int32_t i, c;

-   FAudio_assert(in_channels == 1 || in_channels == 2);
+   FAudio_assert(in_channels == 1 || in_channels == 2 || out_channels == 6);
    FAudio_assert(out_channels == 1 || out_channels == 2 || out_channels == 6);

    FAudio_zero(reverb, sizeof(DspReverb));
@@ -1015,6 +1015,65 @@ static inline float DspReverb_INTERNAL_Process_2_to_5p1(
    return squared_sum;
 }

+static inline float DspReverb_INTERNAL_Process_5p1_to_5p1(
+   DspReverb *reverb,
+   float *restrict samples_in,
+   float *restrict samples_out,
+   size_t sample_count
+) {
+   const float *in_end = samples_in + sample_count;
+   float in, in_ratio, early, late[4];
+   float squared_sum = 0;
+   int32_t c;
+
+   while (samples_in < in_end)
+   {
+#if 0
+       /* Input - Combine 2 channels into 1 */
+       in = (samples_in[0] + samples_in[1]) / 2.0f;
+       in_ratio = in * reverb->dry_ratio;
+       samples_in += 2;
+
+       /* Early Reflections */
+       early = DspReverb_INTERNAL_ProcessEarly(reverb, in);
+
+       /* Reverberation with Wet/Dry Mix */
+       for (c = 0; c < 4; c += 1)
+       {
+           late[c] = (DspReverb_INTERNAL_ProcessChannel(
+               reverb,
+               &reverb->channel[c],
+               early
+           ) * reverb->wet_ratio) + in_ratio;
+           squared_sum += late[c] * late[c];
+       }
+
+       /* Output */
+       *samples_out++ = late[0];   /* Front Left */
+       *samples_out++ = late[1];   /* Front Right */
+       *samples_out++ = 0.0f;      /* Center */
+       *samples_out++ = 0.0f;      /* LFE */
+       *samples_out++ = late[2];   /* Rear Left */
+       *samples_out++ = late[3];   /* Rear Right */
+#endif
+
+       squared_sum += *samples_in * *samples_in;
+       *samples_out++ = *samples_in++; /* Front Left */
+       squared_sum += *samples_in * *samples_in;
+       *samples_out++ = *samples_in++; /* Front Right */
+       squared_sum += *samples_in * *samples_in;
+       *samples_out++ = *samples_in++;     /* Center */
+       squared_sum += *samples_in * *samples_in;
+       *samples_out++ = *samples_in++;     /* LFE */
+       squared_sum += *samples_in * *samples_in;
+       *samples_out++ = *samples_in++; /* Rear Left */
+       squared_sum += *samples_in * *samples_in;
+       *samples_out++ = *samples_in++; /* Rear Right */
+   }
+
+   return squared_sum;
+}
+
 #undef OUTPUT_SAMPLE

 /* Reverb FAPO Implementation */
@@ -1252,7 +1311,9 @@ uint32_t FAudioFXReverb_LockForProcess(
             pOutputLockedParameters->pFormat->nChannels == 6)) ||
        (pInputLockedParameters->pFormat->nChannels == 2 &&
            (pOutputLockedParameters->pFormat->nChannels == 2 ||
-            pOutputLockedParameters->pFormat->nChannels == 6))))
+            pOutputLockedParameters->pFormat->nChannels == 6)) ||
+       (pInputLockedParameters->pFormat->nChannels == 6 &&
+            pOutputLockedParameters->pFormat->nChannels == 6)))
    {
        return FAPO_E_FORMAT_UNSUPPORTED;
    }
@@ -1295,7 +1356,7 @@ static inline void FAudioFXReverb_CopyBuffer(
        return;
    }

-   /* 1 -> 1 or 2 -> 2 */
+   /* 1 -> 1, 2 -> 2, 5.1 -> 5.1 */
    if (fapo->inBlockAlign == fapo->outBlockAlign)
    {
        FAudio_memcpy(
@@ -1421,10 +1482,14 @@ void FAudioFXReverb_Process(
            {
                total = PROCESS(1, 5p1);
            }
-           else
+           else if (fapo->reverb.in_channels == 2)
            {
                total = PROCESS(2, 5p1);
            }
+           else
+           {
+               total = PROCESS(5p1, 5p1);
+           }
            break;
    }
    #undef PROCESS
aeikum commented 3 years ago

See #262. Supporting 5p1->5p1 reverb is enough to get sound working for me in the game. I didn't see any evidence that it tries to create an echo fx.

flibitijibibo commented 3 years ago

Should be fixed in master now.