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). */
2459static 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+
3578int 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);
0 commit comments