/* Copyright (c) 2026 Xiph.Org Foundation */ /* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #if defined(ENABLE_PFA) #include "mdct.h" #include "kiss_fft.h" #include "_kiss_fft_guts.h" #include "stack_alloc.h" #include "mathops.h" #include #include "celt_tx_tables.h" #if defined(OPUS_ARM_TX_MDCT) && defined(OPUS_ARM_PRESUME_NEON_INTR) #define USE_ARM_TX_MDCT #endif #define COS_2PI_5 celt_tx_tab_53[0] #define COS_4PI_5 celt_tx_tab_53[2] #define SIN_2PI_5 celt_tx_tab_53[4] #define SIN_4PI_5 celt_tx_tab_53[6] #define SIN_2PI_3 celt_tx_tab_53[8] #ifdef FIXED_POINT static void pfa_downshift(kiss_fft_cpx *x, int N, int *total, int step) { int i; int shift = IMIN(step, *total); *total -= shift; if (shift == 1) { for (i = 0; i < N; i++) { x[i].r = SHR32(x[i].r, 1); x[i].i = SHR32(x[i].i, 1); } } else if (shift > 0) { for (i = 0; i < N; i++) { x[i].r = PSHR32(x[i].r, shift); x[i].i = PSHR32(x[i].i, shift); } } } #define PFA_DOWNSHIFT(x, N, total, step) pfa_downshift(x, N, total, step) #else #define PFA_DOWNSHIFT(x, N, total, step) #endif #ifndef USE_ARM_TX_MDCT static const opus_int16 p4[4] = { 0, 2, 1, 3 }; static const opus_int16 p8[8] = { 0, 4, 2, 6, 1, 5, 7, 3 }; static const opus_int16 p16[16] = { 0, 8, 4, 12, 2, 10, 14, 6, 1, 9, 5, 13, 15, 7, 3, 11 }; static const opus_int16 p32[32] = { 0, 16, 8, 24, 4, 20, 28, 12, 2, 18, 10, 26, 30, 14, 6, 22, 1, 17, 9, 25, 5, 21, 29, 13, 31, 15, 7, 23, 3, 19, 27, 11 }; #ifdef ENABLE_QEXT static const opus_int16 p64[64] = { 0, 32, 16, 48, 8, 40, 56, 24, 4, 36, 20, 52, 60, 28, 12, 44, 2, 34, 18, 50, 10, 42, 58, 26, 62, 30, 14, 46, 6, 38, 54, 22, 1, 33, 17, 49, 9, 41, 57, 25, 5, 37, 21, 53, 61, 29, 13, 45, 63, 31, 15, 47, 7, 39, 55, 23, 3, 35, 19, 51, 59, 27, 11, 43 }; #endif static OPUS_INLINE const opus_int16 *get_sr_perm_table(int M) { if (M == 4) return p4; if (M == 8) return p8; if (M == 16) return p16; if (M == 32) return p32; #ifdef ENABLE_QEXT if (M == 64) return p64; #endif return NULL; } #define BF(x, y, a, b) \ do { \ x = SUB32_ovflw(a, b); \ y = ADD32_ovflw(a, b); \ } while (0) #define CMUL(dre, dim, are, aim, bre, bim) \ do { \ (dre) = SUB32_ovflw(S_MUL(are, bre), S_MUL(aim, bim)); \ (dim) = ADD32_ovflw(S_MUL(are, bim), S_MUL(aim, bre)); \ } while (0) #define CMUL_CONJ(dre, dim, are, aim, bre, bim) \ do { \ (dre) = ADD32_ovflw(S_MUL(are, bre), S_MUL(aim, bim)); \ (dim) = SUB32_ovflw(S_MUL(aim, bre), S_MUL(are, bim)); \ } while (0) #define BUTTERFLIES(a0, a1, a2, a3) \ do { \ r0 = a0.r; \ i0 = a0.i; \ r1 = a1.r; \ i1 = a1.i; \ BF(t3, t5, t5, t1); \ BF(a2.r, a0.r, r0, t5); \ BF(a3.i, a1.i, i1, t3); \ BF(t4, t6, t2, t6); \ BF(a3.r, a1.r, r1, t4); \ BF(a2.i, a0.i, i0, t6); \ } while (0) #define TRANSFORM(a0, a1, a2, a3, wre, wim) \ do { \ CMUL_CONJ(t1, t2, a2.r, a2.i, wre, wim); \ CMUL(t5, t6, a3.r, a3.i, wre, wim); \ BUTTERFLIES(a0, a1, a2, a3); \ } while (0) static OPUS_INLINE void celt_tx_fft_sr_combine(kiss_fft_cpx *z, const kiss_twiddle_scalar *cos_tab, int len) { int o1 = 2*len; int o2 = 4*len; int o3 = 6*len; const kiss_twiddle_scalar *wim = cos_tab + o1 - 7; kiss_fft_scalar t1, t2, t3, t4, t5, t6, r0, i0, r1, i1; int i; for (i = 0; i < len; i += 4) { TRANSFORM(z[0], z[o1 + 0], z[o2 + 0], z[o3 + 0], cos_tab[0], wim[7]); TRANSFORM(z[2], z[o1 + 2], z[o2 + 2], z[o3 + 2], cos_tab[2], wim[5]); TRANSFORM(z[4], z[o1 + 4], z[o2 + 4], z[o3 + 4], cos_tab[4], wim[3]); TRANSFORM(z[6], z[o1 + 6], z[o2 + 6], z[o3 + 6], cos_tab[6], wim[1]); TRANSFORM(z[1], z[o1 + 1], z[o2 + 1], z[o3 + 1], cos_tab[1], wim[6]); TRANSFORM(z[3], z[o1 + 3], z[o2 + 3], z[o3 + 3], cos_tab[3], wim[4]); TRANSFORM(z[5], z[o1 + 5], z[o2 + 5], z[o3 + 5], cos_tab[5], wim[2]); TRANSFORM(z[7], z[o1 + 7], z[o2 + 7], z[o3 + 7], cos_tab[7], wim[0]); z += 2*4; cos_tab += 2*4; wim -= 2*4; } } static OPUS_INLINE void celt_tx_fft2(kiss_fft_cpx *dst, const kiss_fft_cpx *src) { kiss_fft_cpx tmp; BF(tmp.r, dst[0].r, src[0].r, src[1].r); BF(tmp.i, dst[0].i, src[0].i, src[1].i); dst[1] = tmp; } static OPUS_INLINE void celt_tx_fft4(kiss_fft_cpx *dst, const kiss_fft_cpx *src) { kiss_fft_scalar t1, t2, t3, t4, t5, t6, t7, t8; BF(t3, t1, src[0].r, src[1].r); BF(t8, t6, src[3].r, src[2].r); BF(dst[2].r, dst[0].r, t1, t6); BF(t4, t2, src[0].i, src[1].i); BF(t7, t5, src[2].i, src[3].i); BF(dst[3].i, dst[1].i, t4, t8); BF(dst[3].r, dst[1].r, t3, t7); BF(dst[2].i, dst[0].i, t2, t5); } static OPUS_INLINE void celt_tx_fft8(kiss_fft_cpx *dst, const kiss_fft_cpx *src) { kiss_fft_scalar t1, t2, t3, t4, t5, t6, r0, i0, r1, i1; kiss_twiddle_scalar cos_val = celt_tx_tab_32[4]; celt_tx_fft4(dst, src); t1 = ADD32_ovflw(src[4].r, src[5].r); dst[5].r = SUB32_ovflw(src[4].r, src[5].r); t2 = ADD32_ovflw(src[4].i, src[5].i); dst[5].i = SUB32_ovflw(src[4].i, src[5].i); t5 = ADD32_ovflw(src[6].r, src[7].r); dst[7].r = SUB32_ovflw(src[6].r, src[7].r); t6 = ADD32_ovflw(src[6].i, src[7].i); dst[7].i = SUB32_ovflw(src[6].i, src[7].i); BUTTERFLIES(dst[0], dst[2], dst[4], dst[6]); TRANSFORM(dst[1], dst[3], dst[5], dst[7], cos_val, cos_val); } static OPUS_INLINE void celt_tx_fft16(kiss_fft_cpx *dst, const kiss_fft_cpx *src) { kiss_fft_scalar t1, t2, t3, t4, t5, t6, r0, i0, r1, i1; kiss_twiddle_scalar cos_16_1 = celt_tx_tab_32[2]; kiss_twiddle_scalar cos_16_2 = celt_tx_tab_32[4]; kiss_twiddle_scalar cos_16_3 = celt_tx_tab_32[6]; celt_tx_fft8(dst + 0, src + 0); celt_tx_fft4(dst + 8, src + 8); celt_tx_fft4(dst + 12, src + 12); t1 = dst[ 8].r; t2 = dst[ 8].i; t5 = dst[12].r; t6 = dst[12].i; BUTTERFLIES(dst[0], dst[4], dst[8], dst[12]); TRANSFORM(dst[ 2], dst[ 6], dst[10], dst[14], cos_16_2, cos_16_2); TRANSFORM(dst[ 1], dst[ 5], dst[ 9], dst[13], cos_16_1, cos_16_3); TRANSFORM(dst[ 3], dst[ 7], dst[11], dst[15], cos_16_3, cos_16_1); } static OPUS_INLINE void celt_tx_fft32(kiss_fft_cpx *dst, const kiss_fft_cpx *src) { celt_tx_fft16(dst, src); celt_tx_fft8(dst + 16, src + 16); celt_tx_fft8(dst + 24, src + 24); celt_tx_fft_sr_combine(dst, celt_tx_tab_32, 4); } static OPUS_INLINE void celt_tx_fft64(kiss_fft_cpx *dst, const kiss_fft_cpx *src) { celt_tx_fft32(dst, src); celt_tx_fft16(dst + 32, src + 32); celt_tx_fft16(dst + 48, src + 48); celt_tx_fft_sr_combine(dst, celt_tx_tab_64, 8); } static void celt_tx_fft_sr_c(kiss_fft_cpx *dst, kiss_fft_cpx *src, int N ARG_FIXED(int *downshift_ptr)) { #ifdef FIXED_POINT int stages = celt_ilog2(N); PFA_DOWNSHIFT((kiss_fft_cpx*)src, N, downshift_ptr, stages); #endif switch (N) { case 2: celt_tx_fft2(dst, src); break; case 4: celt_tx_fft4(dst, src); break; case 8: celt_tx_fft8(dst, src); break; case 16: celt_tx_fft16(dst, src); break; case 32: celt_tx_fft32(dst, src); break; case 64: celt_tx_fft64(dst, src); break; default: celt_assert2(0, "Unsupported Split-Radix FFT size"); } #ifdef FIXED_POINT PFA_DOWNSHIFT(dst, N, downshift_ptr, *downshift_ptr); #endif } /* * 15-point Good-Thomas Prime Factor Algorithm (PFA) DFT core. * Mathematically identical to FFT15_CORE from celt_tx_neon.S. */ static void winograd_fft3(const kiss_fft_cpx *in0, const kiss_fft_cpx *in1, const kiss_fft_cpx *in2, kiss_fft_cpx *out0, kiss_fft_cpx *out1, kiss_fft_cpx *out2) { kiss_fft_scalar r_sum12, r_diff12, i_sum12, i_diff12; kiss_fft_scalar t1_r, t1_i, t2_r, t2_i; r_sum12 = ADD32_ovflw(in1->r, in2->r); r_diff12 = SUB32_ovflw(in1->r, in2->r); i_sum12 = ADD32_ovflw(in1->i, in2->i); i_diff12 = SUB32_ovflw(in1->i, in2->i); out0->r = ADD32_ovflw(in0->r, r_sum12); out0->i = ADD32_ovflw(in0->i, i_sum12); t1_r = S_MUL(i_diff12, SIN_2PI_3); t1_i = S_MUL(r_diff12, SIN_2PI_3); t2_r = HALF32(r_sum12); t2_i = HALF32(i_sum12); out1->r = ADD32_ovflw(SUB32_ovflw(in0->r, t2_r), t1_r); out1->i = SUB32_ovflw(SUB32_ovflw(in0->i, t2_i), t1_i); out2->r = SUB32_ovflw(SUB32_ovflw(in0->r, t2_r), t1_r); out2->i = ADD32_ovflw(SUB32_ovflw(in0->i, t2_i), t1_i); } static OPUS_INLINE void winograd_fft5(const kiss_fft_cpx *in, int idx0, int idx1, int idx2, int idx3, int idx4, kiss_fft_cpx *out, int stride) { kiss_fft_cpx dc; kiss_fft_scalar r_sum14, r_diff14, i_sum14, i_diff14; kiss_fft_scalar r_sum23, r_diff23, i_sum23, i_diff23; kiss_fft_scalar r_t4, r_t0, i_t4, i_t0; kiss_fft_scalar r_t5, r_t1, i_t5, i_t1; int s0 = idx0 * stride; int s1 = idx1 * stride; int s2 = idx2 * stride; int s3 = idx3 * stride; int s4 = idx4 * stride; dc = in[0]; r_diff14 = SUB32_ovflw(in[1].r, in[4].r); r_sum14 = ADD32_ovflw(in[1].r, in[4].r); i_diff14 = SUB32_ovflw(in[1].i, in[4].i); i_sum14 = ADD32_ovflw(in[1].i, in[4].i); r_diff23 = SUB32_ovflw(in[2].r, in[3].r); r_sum23 = ADD32_ovflw(in[2].r, in[3].r); i_diff23 = SUB32_ovflw(in[2].i, in[3].i); i_sum23 = ADD32_ovflw(in[2].i, in[3].i); out[s0].r = ADD32_ovflw(dc.r, ADD32_ovflw(r_sum14, r_sum23)); out[s0].i = ADD32_ovflw(dc.i, ADD32_ovflw(i_sum14, i_sum23)); r_t4 = SUB32_ovflw(S_MUL(r_sum14, COS_2PI_5), S_MUL(r_sum23, COS_4PI_5)); r_t0 = SUB32_ovflw(S_MUL(r_sum23, COS_2PI_5), S_MUL(r_sum14, COS_4PI_5)); i_t4 = SUB32_ovflw(S_MUL(i_sum14, COS_2PI_5), S_MUL(i_sum23, COS_4PI_5)); i_t0 = SUB32_ovflw(S_MUL(i_sum23, COS_2PI_5), S_MUL(i_sum14, COS_4PI_5)); r_t5 = ADD32_ovflw(S_MUL(i_diff14, SIN_2PI_5), S_MUL(i_diff23, SIN_4PI_5)); r_t1 = SUB32_ovflw(S_MUL(i_diff14, SIN_4PI_5), S_MUL(i_diff23, SIN_2PI_5)); i_t5 = NEG32_ovflw(ADD32_ovflw(S_MUL(r_diff14, SIN_2PI_5), S_MUL(r_diff23, SIN_4PI_5))); i_t1 = SUB32_ovflw(S_MUL(r_diff23, SIN_2PI_5), S_MUL(r_diff14, SIN_4PI_5)); kiss_fft_scalar r_base4 = ADD32_ovflw(dc.r, r_t4); kiss_fft_scalar i_base4 = ADD32_ovflw(dc.i, i_t4); kiss_fft_scalar r_base0 = ADD32_ovflw(dc.r, r_t0); kiss_fft_scalar i_base0 = ADD32_ovflw(dc.i, i_t0); out[s1].r = ADD32_ovflw(r_base4, r_t5); out[s1].i = ADD32_ovflw(i_base4, i_t5); out[s2].r = ADD32_ovflw(r_base0, r_t1); out[s2].i = ADD32_ovflw(i_base0, i_t1); out[s3].r = SUB32_ovflw(r_base0, r_t1); out[s3].i = SUB32_ovflw(i_base0, i_t1); out[s4].r = SUB32_ovflw(r_base4, r_t5); out[s4].i = SUB32_ovflw(i_base4, i_t5); } static void celt_tx_fft15_c(const kiss_fft_cpx *in, kiss_fft_cpx *out, int stride) { kiss_fft_cpx tmp[15]; /* c5 = 0 */ winograd_fft3(&in[2], &in[0], &in[1], &tmp[0], &tmp[5], &tmp[10]); /* c5 = 1 */ winograd_fft3(&in[13], &in[5], &in[9], &tmp[1], &tmp[6], &tmp[11]); /* c5 = 2 */ winograd_fft3(&in[11], &in[3], &in[7], &tmp[2], &tmp[7], &tmp[12]); /* c5 = 3 */ winograd_fft3(&in[14], &in[6], &in[10], &tmp[3], &tmp[8], &tmp[13]); /* c5 = 4 */ winograd_fft3(&in[12], &in[4], &in[8], &tmp[4], &tmp[9], &tmp[14]); winograd_fft5(tmp, 0, 3, 6, 9, 12, out, stride); winograd_fft5(tmp + 5, 5, 8, 11, 14, 2, out, stride); winograd_fft5(tmp + 10, 10, 13, 1, 4, 7, out, stride); } static void celt_tx_fft_pfa_15xM_ns_c(const struct OpusTXContext *s, void *out, void *in, ptrdiff_t stride ARG_FIXED(int downshift)) { int i, j; int len = s->len; int M = s->sub->len; const opus_int16 *perm; kiss_fft_cpx *tmp = (kiss_fft_cpx *)s->tmp; kiss_fft_cpx *in_cpx = (kiss_fft_cpx *)in; kiss_fft_cpx *out_cpx = (kiss_fft_cpx *)out; (void)stride; perm = get_sr_perm_table(M); celt_assert(perm != NULL); PFA_DOWNSHIFT(in_cpx, len, &downshift, 3); for (i = 0; i < M; i++) { celt_tx_fft15_c(in_cpx + 15 * perm[i], tmp + i, M); } PFA_DOWNSHIFT(tmp, len, &downshift, 2); { #ifdef FIXED_POINT int sub_shift; #endif for (j = 0; j < 15; j++) { kiss_fft_cpx *row = tmp + j * M; #ifdef FIXED_POINT sub_shift = downshift; #endif celt_tx_fft_sr_c(row, row, M ARG_FIXED(&sub_shift)); } } for (i = 0; i < len; i++) { out_cpx[i] = tmp[s->map[i]]; } } static const struct OpusTXContext celt_tx_p2_4_c = { 4, 1, celt_tx_p2_map_4, NULL, NULL, NULL, NULL }; static const struct OpusTXContext celt_tx_p2_8_c = { 8, 1, celt_tx_p2_map_8, NULL, NULL, NULL, NULL }; static const struct OpusTXContext celt_tx_p2_16_c = { 16, 1, celt_tx_p2_map_16, NULL, NULL, NULL, NULL }; static const struct OpusTXContext celt_tx_p2_32_c = { 32, 1, celt_tx_p2_map_32, NULL, NULL, NULL, NULL }; #if defined(ENABLE_QEXT) static const struct OpusTXContext celt_tx_p2_64_c = { 64, 1, celt_tx_p2_map_64, NULL, NULL, NULL, NULL }; #endif static const struct OpusTXContext celt_tx_pfa_60_c = { 60, 1, celt_tx_pfa_map_60, NULL, NULL, &celt_tx_p2_4_c, NULL }; static const struct OpusTXContext celt_tx_pfa_120_c = { 120, 1, celt_tx_pfa_map_120, NULL, NULL, &celt_tx_p2_8_c, NULL }; static const struct OpusTXContext celt_tx_pfa_240_c = { 240, 1, celt_tx_pfa_map_240, NULL, NULL, &celt_tx_p2_16_c, NULL }; static const struct OpusTXContext celt_tx_pfa_480_c = { 480, 1, celt_tx_pfa_map_480, NULL, NULL, &celt_tx_p2_32_c, NULL }; #if defined(ENABLE_QEXT) static const struct OpusTXContext celt_tx_pfa_960_c = { 960, 1, celt_tx_pfa_map_960, NULL, NULL, &celt_tx_p2_64_c, NULL }; #endif static const struct OpusTXContext celt_tx_mdct_120_c = { 120, 1, celt_tx_mdct_map_120, NULL, NULL, &celt_tx_pfa_60_c, celt_tx_fft_pfa_15xM_ns_c }; static const struct OpusTXContext celt_tx_mdct_240_c = { 240, 1, celt_tx_mdct_map_240, NULL, NULL, &celt_tx_pfa_120_c, celt_tx_fft_pfa_15xM_ns_c }; static const struct OpusTXContext celt_tx_mdct_480_c = { 480, 1, celt_tx_mdct_map_480, NULL, NULL, &celt_tx_pfa_240_c, celt_tx_fft_pfa_15xM_ns_c }; static const struct OpusTXContext celt_tx_mdct_960_c = { 960, 1, celt_tx_mdct_map_960, NULL, NULL, &celt_tx_pfa_480_c, celt_tx_fft_pfa_15xM_ns_c }; #if defined(ENABLE_QEXT) static const struct OpusTXContext celt_tx_mdct_1920_c = { 1920, 1, celt_tx_mdct_map_1920, NULL, NULL, &celt_tx_pfa_960_c, celt_tx_fft_pfa_15xM_ns_c }; #endif static const struct OpusTXContext *celt_tx_mdct_kernel_c(int len) { switch (len) { case 120: return &celt_tx_mdct_120_c; case 240: return &celt_tx_mdct_240_c; case 480: return &celt_tx_mdct_480_c; case 960: return &celt_tx_mdct_960_c; #if defined(ENABLE_QEXT) case 1920: return &celt_tx_mdct_1920_c; #endif default: return NULL; } } #endif #if defined(CUSTOM_MODES) || defined(ENABLE_OPUS_CUSTOM_API) || defined(ENABLE_DEEP_PLC) static OPUS_INLINE void pfa_copy_bitrev_input(const kiss_fft_state *st, const kiss_fft_cpx *fin, kiss_fft_cpx *fout) { int i; int nfft = st->nfft; if (fin == fout) { VARDECL(kiss_fft_cpx, tmp_perm); SAVE_STACK; ALLOC(tmp_perm, nfft, kiss_fft_cpx); OPUS_COPY(tmp_perm, fin, nfft); for (i = 0; i < nfft; i++) fout[st->bitrev[i]] = tmp_perm[i]; RESTORE_STACK; } else { for (i = 0; i < nfft; i++) fout[st->bitrev[i]] = fin[i]; } } #endif static OPUS_INLINE const struct OpusTXContext *get_pfa_context(int nfft, const struct OpusTXContext **mdct_tpl) { #if defined(USE_ARM_TX_MDCT) *mdct_tpl = celt_tx_mdct_kernel(2 * nfft); return *mdct_tpl ? (*mdct_tpl)->sub : NULL; #else *mdct_tpl = celt_tx_mdct_kernel_c(2 * nfft); return (*mdct_tpl && (*mdct_tpl)->fn == celt_tx_fft_pfa_15xM_ns_c) ? (*mdct_tpl)->sub : NULL; #endif } static void opus_pfa_impl(const kiss_fft_state *st, const kiss_fft_cpx *fin, kiss_fft_cpx *fout, int is_inverse ARG_FIXED(int shift)) { int i; int nfft = st->nfft; const opus_int16 *pfa_map; const struct OpusTXContext *mdct_tpl; const struct OpusTXContext *tpl = get_pfa_context(nfft, &mdct_tpl); struct OpusTXContext pfa; VARDECL(kiss_fft_cpx, tmp); SAVE_STACK; #if defined(CUSTOM_MODES) || defined(ENABLE_OPUS_CUSTOM_API) || defined(ENABLE_DEEP_PLC) if (tpl == NULL) { pfa_copy_bitrev_input(st, fin, fout); if (is_inverse) { for (i = 0; i < nfft; i++) fout[i].i = -fout[i].i; } opus_fft_impl(st, fout ARG_FIXED(shift)); if (is_inverse) { for (i = 0; i < nfft; i++) fout[i].i = -fout[i].i; } RESTORE_STACK; return; } #else celt_assert2(tpl != NULL, "PFA FFT/IFFT called with unsupported size in non-custom mode"); #endif ALLOC(tmp, nfft, kiss_fft_cpx); pfa_map = mdct_tpl->map + nfft; if (fin == fout) { OPUS_COPY(tmp, fin, nfft); fin = tmp; } for (i = 0; i < nfft; i++) fout[pfa_map[i]] = fin[i]; pfa = *tpl; pfa.tmp = tmp; #if defined(USE_ARM_TX_MDCT) mdct_tpl->fn(&pfa, fout, fout, sizeof(kiss_fft_cpx) ARG_FIXED(shift)); /* Time-reverse the output from index 1 to N-1 to obtain forward DFT because the Neon assembly computes Inverse DFT by default */ if (!is_inverse) #else celt_tx_fft_pfa_15xM_ns_c(&pfa, fout, fout, 1 ARG_FIXED(shift)); /* Time-reverse the output from index 1 to N-1 to obtain inverse DFT */ if (is_inverse) #endif for (i = 1; i < (nfft + 1) / 2; i++) { kiss_fft_cpx t = fout[i]; fout[i] = fout[nfft - i]; fout[nfft - i] = t; } RESTORE_STACK; } void opus_fft_pfa_c(const kiss_fft_state *st, const kiss_fft_cpx *fin, kiss_fft_cpx *fout ARG_FIXED(int downshift)) { opus_pfa_impl(st, fin, fout, 0 ARG_FIXED(downshift)); } void opus_ifft_pfa_c(const kiss_fft_state *st, const kiss_fft_cpx *fin, kiss_fft_cpx *fout ARG_FIXED(int fft_shift)) { opus_pfa_impl(st, fin, fout, 1 ARG_FIXED(fft_shift)); } #endif /* ENABLE_PFA */