rockbox/lib/rbcodec/codecs/libatrac/atrac3_armv5e.S
Michael Giacomelli b8861e3f09 atrac3: remove the rounding bias of the ARMv5E QMF filter
The ARMv5E dewindowing routine takes 16 of each product's bits,
rounded down, so each sum of 24 products came out about 12 units
low. Through the filter bank that put a dc offset of about 12 LSB
(of 16 bits) on the output and, from the high band, a tone at half
the sample rate; together they were four fifths of the decoder's
error on these targets.

Start the sums 12 up. Also correct a comment in both ARM files.

Checked with perfsim (Sansa Clip+ build) against ffmpeg's decode of
seven files: the dc offset goes from 11 to 12 LSB down to under
1 LSB, and the error from about -66 dBFS to -69 to -72 dBFS, the
same as the ARMv4 routine gives. Decoding is 0.7% slower (26.73 to
26.91 MHz for atrac3_lp2_132.oma). The ARMv4 output is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 21:56:47 -04:00

169 lines
8.1 KiB
ArmAsm

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id:
*
* Copyright (C) 2010 by Michael Giacomelli
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "config.h"
.syntax unified
.section .text, "ax", %progbits
/****************************************************************************
* atrac3_iqmf_dewindowing_armv5e(int32_t *out,
* int32_t *in,
* int32_t *win,
* unsigned int nIn);
*
* Dewindowing step within iqmf of atrac3 synthesis using 16 bit filter
* coefficients and armv5e packed multiply instructions. Uses 2.5 cycles
* per filter coefficient (ideal). Benchmarked 3.54 per coefficient (Clip+).
*
* Reference implementation:
*
* for (j = nIn; j != 0; j--) {
* s1 = fixmul32(in[0], win[0]);
* s2 = fixmul32(in[1], win[1]);
* for (i = 2; i < 48; i += 2) {
* s1 += fixmul32(in[i ], win[i ]);
* s2 += fixmul32(in[i+1], win[i+1]);
* }
* out[0] = s2 << 1;
* out[1] = s1 << 1;
* in += 2;
* out += 2;
* }
* Note: r12 is a scratch register and can be used without restorage.
****************************************************************************/
.align 2
.global atrac3_iqmf_dewindowing_armv5e
.type atrac3_iqmf_dewindowing_armv5e, %function
atrac3_iqmf_dewindowing_armv5e:
/* r0 = dest */
/* r1 = input samples */
/* r2 = window coefficients */
/* r3 = counter */
stmfd sp!, {r4-r11, lr} /* save non-scratch registers */
.iqmf_dewindow_outer_loop: /* outer loop 0...counter-1 */
/* 0.. 7 */
ldmia r2!, {r4, r5, r8, r9} /* load win[0..7] */
ldmia r1!, {r6, r7, r10, r11} /* load in[0..3] to avoid stall on arm11 */
/* Each of the 24 products of a sum is rounded down by half a unit on
* average. Start the sums 12 up to make up for it; without that the
* output has a dc offset and, from the high band, a tone at fs/2. */
mov lr, #12
mov r12, #12
smlawb lr, r6, r4, lr /* s1 += in[0] * win[0] >> 16 */
smlawt r12, r7, r4, r12 /* s2 += in[1] * win[1] >> 16 */
smlawb lr, r10, r5, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r11,r5, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
ldmia r1!, {r6, r7, r10, r11} /* load in[i...i+3] */
smlawb lr, r6, r8, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r7, r8, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
smlawb lr, r10, r9, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r11, r9, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
/* 8..15 */
ldmia r2!, {r4, r5, r8, r9} /* load win[8..15] */
ldmia r1!, {r6, r7, r10, r11} /* load in[i...i+3] */
smlawb lr, r6, r4, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r7, r4, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
smlawb lr, r10, r5, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r11,r5, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
ldmia r1!, {r6, r7, r10, r11} /* load in[i...i+3] */
smlawb lr, r6, r8, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r7, r8, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
smlawb lr, r10, r9, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r11,r9, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
/* 16..23 */
ldmia r2!, {r4, r5, r8, r9} /* load win[16..23] */
ldmia r1!, {r6, r7, r10, r11} /* load in[i...i+3] */
smlawb lr, r6, r4, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r7, r4, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
smlawb lr, r10, r5, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r11,r5, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
ldmia r1!, {r6, r7, r10, r11} /* load in[i...i+3] */
smlawb lr, r6, r8, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r7, r8, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
smlawb lr, r10, r9, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r11,r9, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
/* 24..31 */
ldmia r2!, {r4, r5, r8, r9} /* load win[24..31] */
ldmia r1!, {r6, r7, r10, r11} /* load in[i...i+3] */
smlawb lr, r6, r4, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r7, r4, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
smlawb lr, r10, r5, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r11,r5, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
ldmia r1!, {r6, r7, r10, r11} /* load in[i...i+3] */
smlawb lr, r6, r8, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r7, r8, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
smlawb lr, r10, r9, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r11,r9, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
/* 32..39 */
ldmia r2!, {r4, r5, r8, r9} /* load win[32..39] */
ldmia r1!, {r6, r7, r10, r11} /* load in[i...i+3] */
smlawb lr, r6, r4, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r7, r4, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
smlawb lr, r10, r5, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r11,r5, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
ldmia r1!, {r6, r7, r10, r11} /* load in[i...i+3] */
smlawb lr, r6, r8, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r7, r8, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
smlawb lr, r10, r9, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r11,r9, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
/* 40..47 */
ldmia r2!, {r4, r5, r8, r9} /* load win[40..47] */
ldmia r1!, {r6, r7, r10, r11} /* load in[i...i+3] */
smlawb lr, r6, r4, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r7, r4, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
smlawb lr, r10, r5, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r11,r5, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
ldmia r1!, {r6, r7, r10, r11} /* load in[i...i+3] */
smlawb lr, r6, r8, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r7, r8, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
smlawb lr, r10, r9, lr /* s1 += in[i ] * win[i ] >> 16 */
smlawt r12, r11,r9, r12 /* s2 += in[i+1] * win[i+1] >> 16 */
mov lr , lr , lsl #1
mov r12, r12, lsl #1
stmia r0!, {r12, lr} /* store result out[0]=s2, out[1]=s1 */
sub r1, r1, #184 /* roll back 46 entries = 184 bytes */
sub r2, r2, #96 /* roll back 48 entries * 2 bytes = 96 bytes = win[0] */
subs r3, r3, #1 /* outer loop -= 1 */
bgt .iqmf_dewindow_outer_loop
ldmpc regs=r4-r11 /* restore registers */
.atrac3_iqmf_dewindowing_armv5e_end:
.size atrac3_iqmf_dewindowing_armv5e,.atrac3_iqmf_dewindowing_armv5e_end-atrac3_iqmf_dewindowing_armv5e