[PATCH] libmp3lame: fix float->double type mismatch in analyzer xr copy

"Maya R. Odinezenko via Lame-dev" <[email protected]> Thu, 16 Jul 2026 15:50:40 -0400
Newsgroups gmane.comp.audio.mp3.lame
Message-ID <CAFCgL3njcp3EBrXLQ=3XHeQaoUJdSostPr=kzBfRuqJRxyr9hQ@mail.gmail.com>
Hello,

While porting the frame-analyzer UI to macOS on top of libmp3lame's
plotting hooks, the encoder-side MDCT data arrived as garbage. The
cause is a type mismatch in the analyzer copy in
lame_encode_mp3_frame() (encoder.c:472): pinfo->xr is double[576]
(lame-analysis.h) while tt[].xr uses FLOAT, normally float
(machine.h). memcpy copies bytes and performs no conversion, so in
the default build it moves sizeof(FLOAT)*576 = 2304 bytes into the
4608-byte double destination: the first half holds reinterpreted bit
patterns and the second half is never written by the copy. The
bundled gtk mp3x renders its MDCT panes from the same arrays. The
decoder-side mpg123xr path already produces doubles and is fine, and
FLOAT=double builds are accidentally unaffected, which may be why
this went unnoticed.

The patch converts element by element, which performs the proper
float-to-double conversion and is correct for either FLOAT width.

Testing, all on the 4.0 release (Apple silicon):

- Normal-encoding regression checks (analysis disabled): pristine vs
  patched builds produce byte-identical MP3s on the bundled
  testcase.wav (cmp) at -V2 and at
  -m s -b 320 -q 0 --strictly-enforce-ISO --noreplaygain.

- Analyzer-enabled harness (lame_set_analysis(1), caller-owned
  pinfo attached, deterministic noise input): the changed branch
  executes in both builds. Pristine, last frame, xr[0][0]: only the
  first 288 of 576 values are ever written, nonzero magnitudes
  between 2.1e-38 and 5.2e-9, mostly not representable as float.
  Patched: bins populated up to the encoder's lowpass, all 576
  values are exact float-to-double conversions, magnitudes between
  3.7e-5 and 0.18. The analyzer-enabled encodes of the two builds
  are byte-identical.

- The full analyzer path was also exercised through the macOS
  analyzer port: before the fix the encoder-side MDCT pane shows
  garbage, after it the display is correct.

The patch below is against current trunk (r6586); it applies
unchanged to the 4.0 release.

Maya

-- >8 --
From: Maya <[email protected]>
Date: Thu, 16 Jul 2026 15:28:13 -0400
Subject: [PATCH] libmp3lame: fix float->double type mismatch in analyzer xr
 copy

lame_encode_mp3_frame() fills the frame-analyzer data with

    memcpy(gfc->pinfo->xr[gr][ch], &gfc->l3_side.tt[gr][ch].xr[0],
           sizeof(FLOAT) * 576);

but pinfo->xr is double[576] (lame-analysis.h) while tt[].xr uses
FLOAT, normally float (machine.h). memcpy copies bytes and performs
no conversion: in the default build it moves 2304 bytes of float bit
patterns into the 4608-byte double destination, so the first half
holds reinterpreted values and the second half is never written by
the copy. The encoder-side MDCT display in the analyzer shows
garbage as a result. The decoder-side mpg123xr path already produces
doubles and is unaffected, and builds configured with FLOAT=double
are accidentally unaffected.

Convert element by element instead, which performs the proper
float-to-double conversion and is correct for either FLOAT width.
---
 libmp3lame/encoder.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/libmp3lame/encoder.c b/libmp3lame/encoder.c
index d123d69..8ba6ac9 100644
--- a/libmp3lame/encoder.c
+++ b/libmp3lame/encoder.c
@@ -469,7 +469,13 @@ lame_encode_mp3_frame(       /* Output */
                 gfc->pinfo->ms_ener_ratio[gr] = ms_ener_ratio[gr];
                 gfc->pinfo->blocktype[gr][ch] =
gfc->l3_side.tt[gr][ch].block_type;
                 gfc->pinfo->pe[gr][ch] = pe_use[gr][ch];
-                memcpy(gfc->pinfo->xr[gr][ch],
&gfc->l3_side.tt[gr][ch].xr[0], sizeof(FLOAT) * 576);
+                /* pinfo->xr is double but tt[].xr is FLOAT: convert
+                   element-wise, a raw copy would reinterpret the bits */
+                {
+                    int     i;
+                    for (i = 0; i < 576; ++i)
+                        gfc->pinfo->xr[gr][ch][i] =
gfc->l3_side.tt[gr][ch].xr[i];
+                }
                 /* in psymodel, LR and MS data was stored in pinfo.
                    switch to MS data: */
                 if (gfc->ov_enc.mode_ext == MPG_MD_MS_LR) {