Skip to content

Commit f7e707b

Browse files
committed
s24 & s32 tests: tolerate small x87 FP mismatches in s24/s32 render identity
1 parent cf1b574 commit f7e707b

2 files changed

Lines changed: 195 additions & 40 deletions

File tree

test/test_synth_render_s24.cpp

Lines changed: 110 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,92 @@
11
/*
22
* FluidSynth - A Software Synthesizer
33
*
4-
* Lighytweight s24 render identity test (float-oracle).
4+
* Lightweight s24 render identity test (float-oracle).
55
*
66
* Verifies that fluid_synth_write_s24() matches a local reference conversion
7-
* computed from fluid_synth_write_float() using the same s32 scale + round + clip + mask
7+
* computed from fluid_synth_write_float() using the same scale + round+clip
88
* semantics as the s24 renderer. No golden EXPECTED buffer yet.
9+
*
10+
* Note: On 32-bit x86 builds without SSE2 (x87 FPU), floating-point evaluation
11+
* can differ slightly between render paths (excess precision / double rounding),
12+
* leading to rare, small LSB-level deltas at the quantization boundary. This test
13+
* tolerates a bounded number of small mismatches on such builds.
914
*/
1015

1116
#include "test.h"
1217
#include "fluidsynth.h"
13-
#include "drivers/fluid_audio_convert.h"
1418

15-
#include <cstdint>
16-
#include <cstdlib>
17-
#include <cstring>
18-
#include <cstdio>
19+
#include <stdint.h>
20+
#include <stdlib.h>
21+
#include <string.h>
22+
#include <limits.h>
23+
#include <stdio.h>
24+
25+
/*
26+
* Must match the s24 renderer's scale convention in fluid_synth_write_int.cpp.
27+
*
28+
* For s24, we quantize to signed 24-bit range and then store as 24-in-32
29+
* (left-aligned), i.e. final int32 sample has low 8 bits cleared.
30+
*/
31+
#define S24_SCALE (8388606.0f) /* (2^23 - 2) matches the renderer convention */
32+
33+
/* Round+clip to signed 24-bit integer (range [-8388608, 8388607]). */
34+
static int32_t round_clip_to_i24_ref(float x)
35+
{
36+
int64_t i;
1937

20-
/* Must match the s32 scale used by the integer renderer (s24 uses s32 scale + mask). */
21-
#define S32_SCALE (2147483646.0f)
22-
#define S24_MASK (0xFFFFFF00u) /* 24 valid bits left-aligned in 32-bit container */
38+
if (x >= 0.0f)
39+
{
40+
i = (int64_t)(x + 0.5f);
41+
if (i > 8388607)
42+
{
43+
i = 8388607;
44+
}
45+
}
46+
else
47+
{
48+
i = (int64_t)(x - 0.5f);
49+
if (i < -8388608)
50+
{
51+
i = -8388608;
52+
}
53+
}
2354

55+
return (int32_t)i;
56+
}
57+
58+
/* Convert float samples to s24-in-32 (left aligned): (i24 << 8). */
2459
static void float_to_s24_ref(const float *in, int32_t *out, int count)
2560
{
2661
int i;
2762
for (i = 0; i < count; ++i)
2863
{
29-
/* s24 is transported as int32 with the lowest 8 bits cleared (left-aligned 24-bit PCM). */
30-
const int32_t s32 = round_clip_to<int32_t>(in[i] * S32_SCALE);
31-
out[i] = (int32_t)((uint32_t)s32 & (uint32_t)S24_MASK);
64+
int32_t i24 = round_clip_to_i24_ref(in[i] * S24_SCALE);
65+
66+
/* Shift in unsigned domain to avoid UB on negative values. */
67+
uint32_t u = (uint32_t)i24;
68+
u <<= 8;
69+
out[i] = (int32_t)u;
3270
}
3371
}
3472

73+
static int64_t abs_i64(int64_t x)
74+
{
75+
return (x < 0) ? -x : x;
76+
}
77+
3578
int main(void)
3679
{
3780
fluid_settings_t *settings = NULL;
3881
fluid_synth_t *synth_f = NULL; /* float oracle synth */
39-
fluid_synth_t *synth_s24 = NULL; /* s24 render synth (int32 container, low 8 bits zero) */
82+
fluid_synth_t *synth_s24 = NULL; /* s24 render synth */
4083

4184
/* Enough frames to span multiple internal blocks, but still fast. */
4285
const int len = 4096;
4386

4487
float *out_f = NULL; /* interleaved float: L,R,L,R,... */
45-
int32_t *out_s24 = NULL; /* interleaved s24 from API (int32 container) */
46-
int32_t *exp_s24 = NULL; /* interleaved s24 oracle (int32 container) */
88+
int32_t *out_s24 = NULL; /* interleaved s24-in-32 from API */
89+
int32_t *exp_s24 = NULL; /* interleaved s24-in-32 oracle */
4790

4891
int i;
4992

@@ -64,7 +107,7 @@ int main(void)
64107
TEST_SUCCESS(fluid_synth_sfload(synth_f, TEST_SOUNDFONT, 1));
65108
TEST_SUCCESS(fluid_synth_sfload(synth_s24, TEST_SOUNDFONT, 1));
66109

67-
/* Deterministic program + note (apply identically to both synths). */
110+
/* Deterministic program + note (apply identically to both synths) */
68111
TEST_SUCCESS(fluid_synth_program_change(synth_f, 0, 0));
69112
TEST_SUCCESS(fluid_synth_program_change(synth_s24, 0, 0));
70113

@@ -86,34 +129,72 @@ int main(void)
86129
/* Render float (oracle source). Interleaved stereo. */
87130
TEST_SUCCESS(fluid_synth_write_float(synth_f, len, out_f, 0, 2, out_f, 1, 2));
88131

89-
/* Convert float oracle -> expected s24. */
132+
/* Convert float oracle -> expected s24-in-32 */
90133
float_to_s24_ref(out_f, exp_s24, 2 * len);
91134

92135
/* Render s24. Interleaved stereo. */
93136
TEST_SUCCESS(fluid_synth_write_s24(synth_s24, len, out_s24, 0, 2, out_s24, 1, 2));
94137

95-
/* Compare. */
138+
/* Tolerances */
139+
#if defined(__i386__) && !defined(__SSE2__)
140+
const int64_t kTol = 256; /* x87 tolerance for s24-in-32 (1 << 8) */
141+
const int kMaxTol = 16; /* allow a few borderline samples */
142+
#else
143+
const int64_t kTol = 0; /* strict everywhere else */
144+
const int kMaxTol = 0;
145+
#endif
146+
147+
/* Track mismatches */
148+
int tolCount = 0;
149+
int64_t maxAbsDelta = 0;
150+
int worstIdx = -1;
151+
int64_t worstDelta = 0;
152+
153+
/* Compare */
96154
for (i = 0; i < 2 * len; ++i)
97155
{
98-
if (out_s24[i] != exp_s24[i])
156+
int64_t delta = (int64_t)out_s24[i] - (int64_t)exp_s24[i];
157+
int64_t ad = abs_i64(delta);
158+
159+
if (ad > maxAbsDelta)
99160
{
100-
const int64_t delta = (int64_t)out_s24[i] - (int64_t)exp_s24[i];
101-
fprintf(stderr,
102-
"s24 mismatch @%d (interleaved index): exp=%d got=%d delta=%lld\n",
103-
i,
104-
(int)exp_s24[i],
105-
(int)out_s24[i],
106-
(long long)delta);
161+
maxAbsDelta = ad;
162+
worstIdx = i;
163+
worstDelta = delta;
164+
}
165+
166+
if (ad == 0)
167+
{
168+
continue;
169+
}
170+
171+
if (ad > kTol)
172+
{
173+
fprintf(stderr, "s24 mismatch @%d: exp=%d got=%d delta=%ld\n", i, (int)exp_s24[i], (int)out_s24[i], (long)delta);
107174
TEST_ASSERT(0);
108175
}
109176

110-
if ((((uint32_t)out_s24[i]) & 0xFFu) != 0u)
177+
/* ad is non-zero and within tolerance */
178+
tolCount++;
179+
if (tolCount > kMaxTol)
111180
{
112-
fprintf(stderr, "s24 low-byte nonzero @%d: got=0x%08X\n", i, (unsigned)((uint32_t)out_s24[i]));
181+
fprintf(stderr, "Too many tolerated mismatches (count=%d), maxAbsDelta=%ld\n", tolCount, (long)maxAbsDelta);
113182
TEST_ASSERT(0);
114183
}
115184
}
116185

186+
#if defined(__i386__) && !defined(__SSE2__)
187+
if (tolCount > 0)
188+
{
189+
fprintf(stderr,
190+
"x87 tolerated mismatches: count=%d, maxAbsDelta=%ld at idx=%d (delta=%ld)\n",
191+
tolCount,
192+
(long)maxAbsDelta,
193+
worstIdx,
194+
(long)worstDelta);
195+
}
196+
#endif
197+
117198
free(out_f);
118199
free(out_s24);
119200
free(exp_s24);

test/test_synth_render_s32.cpp

Lines changed: 85 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
* FluidSynth - A Software Synthesizer
33
*
4-
* Lighytweight s32 render identity test (float-oracle).
4+
* Lightweight s32 render identity test (float-oracle).
55
*
66
* Verifies that fluid_synth_write_s32() matches a local reference conversion
77
* computed from fluid_synth_write_float() using the same scale + round+clip
@@ -10,25 +10,55 @@
1010

1111
#include "test.h"
1212
#include "fluidsynth.h"
13-
#include "drivers/fluid_audio_convert.h"
1413

1514
#include <stdint.h>
1615
#include <stdlib.h>
1716
#include <string.h>
17+
#include <limits.h>
1818
#include <stdio.h>
1919

2020
/* Must match the s32 renderer's scale convention in fluid_synth_write_int.cpp */
2121
#define S32_SCALE (2147483646.0f)
2222

23+
/* Local float->i32 reference conversion: round+clip, no dithering. */
24+
static int32_t round_clip_to_i32_ref(float x)
25+
{
26+
int64_t i;
27+
28+
if (x >= 0.0f)
29+
{
30+
i = (int64_t)(x + 0.5f);
31+
if (i > INT32_MAX)
32+
{
33+
i = INT32_MAX;
34+
}
35+
}
36+
else
37+
{
38+
i = (int64_t)(x - 0.5f);
39+
if (i < INT32_MIN)
40+
{
41+
i = INT32_MIN;
42+
}
43+
}
44+
45+
return (int32_t)i;
46+
}
47+
2348
static void float_to_s32_ref(const float *in, int32_t *out, int count)
2449
{
2550
int i;
2651
for (i = 0; i < count; ++i)
2752
{
28-
out[i] = round_clip_to<int32_t>(in[i] * S32_SCALE);
53+
out[i] = round_clip_to_i32_ref(in[i] * S32_SCALE);
2954
}
3055
}
3156

57+
static int64_t abs_i64(int64_t x)
58+
{
59+
return (x < 0) ? -x : x;
60+
}
61+
3262
int main(void)
3363
{
3464
fluid_settings_t *settings = NULL;
@@ -89,21 +119,65 @@ int main(void)
89119
/* Render s32. Interleaved stereo. */
90120
TEST_SUCCESS(fluid_synth_write_s32(synth_s32, len, out_s32, 0, 2, out_s32, 1, 2));
91121

122+
/* Tolerances */
123+
#if defined(__i386__) && !defined(__SSE2__)
124+
const int64_t kTol = 8; /* x87 tolerance for s32 */
125+
const int kMaxTol = 16; /* allow a few borderline samples */
126+
#else
127+
const int64_t kTol = 0; /* strict everywhere else */
128+
const int kMaxTol = 0;
129+
#endif
130+
131+
/* Track mismatches */
132+
int tolCount = 0;
133+
int64_t maxAbsDelta = 0;
134+
int worstIdx = -1;
135+
int64_t worstDelta = 0;
136+
92137
/* Compare */
93138
for (i = 0; i < 2 * len; ++i)
94139
{
95-
if (out_s32[i] != exp_s32[i])
140+
int64_t delta = (int64_t)out_s32[i] - (int64_t)exp_s32[i];
141+
int64_t ad = abs_i64(delta);
142+
143+
if (ad > maxAbsDelta)
144+
{
145+
maxAbsDelta = ad;
146+
worstIdx = i;
147+
worstDelta = delta;
148+
}
149+
150+
if (ad == 0)
151+
{
152+
continue;
153+
}
154+
155+
if (ad > kTol)
96156
{
97-
int delta = (int)(out_s32[i] - exp_s32[i]);
98-
fprintf(stderr,
99-
"s32 mismatch @%d (interleaved index): exp=%d got=%d delta=%d\n",
100-
i,
101-
(int)exp_s32[i],
102-
(int)out_s32[i],
103-
delta);
157+
fprintf(stderr, "s32 mismatch @%d: exp=%d got=%d delta=%ld\n", i, (int)exp_s32[i], (int)out_s32[i], (long)delta);
104158
TEST_ASSERT(0);
105159
}
160+
161+
/* ad is non-zero and within tolerance */
162+
tolCount++;
163+
if (tolCount > kMaxTol)
164+
{
165+
fprintf(stderr, "Too many tolerated mismatches (count=%d), maxAbsDelta=%ld\n", tolCount, (long)maxAbsDelta);
166+
TEST_ASSERT(0);
167+
}
168+
}
169+
170+
#if defined(__i386__) && !defined(__SSE2__)
171+
if (tolCount > 0)
172+
{
173+
fprintf(stderr,
174+
"x87 tolerated mismatches: count=%d, maxAbsDelta=%ld at idx=%d (delta=%ld)\n",
175+
tolCount,
176+
(long)maxAbsDelta,
177+
worstIdx,
178+
(long)worstDelta);
106179
}
180+
#endif
107181

108182
free(out_f);
109183
free(out_s32);

0 commit comments

Comments
 (0)