Skip to content

Issue 1737----Add s24/s32 write; extend WASAPI to use s24/s32; extend enumeration to report s24/s32#1746

Merged
derselbst merged 21 commits into
FluidSynth:masterfrom
jimhen3ry:issue-1737
Feb 14, 2026
Merged

Issue 1737----Add s24/s32 write; extend WASAPI to use s24/s32; extend enumeration to report s24/s32#1746
derselbst merged 21 commits into
FluidSynth:masterfrom
jimhen3ry:issue-1737

Conversation

@jimhen3ry

Copy link
Copy Markdown
Contributor

Summary

Refactors synth integer output rendering to a shared C++ implementation (preserving existing s16 behavior bit-for-bit).

Centralizes float→PCM conversion + dithering kernels (s16/s24/s32) in shared internal helpers used by both synth-write and driver paths.

Adds 24-bit and 32-bit integer output support to the WASAPI driver and device enumeration.

Commits, commit-by-commit review suggested

https://github.com/jimhen3ry/fluidsynth_old/commit/12c48c3d05cfe054f9b238b40e5529c59b1e182f synth: refactor integer output rendering core (templated s16/s24/s32)

https://github.com/jimhen3ry/fluidsynth_old/commit/fe7c36560b09474f1d90557d50637c645bfd22f3 tests: add render identity tests for s16/s24/s32 integer paths

https://github.com/jimhen3ry/fluidsynth_old/commit/19252727896617ef31db5f554d10d8b84999d0b0 wasapi: 24/32-bit output via shared planar-float→PCM conversion (centralizes dither/convert kernels)

https://github.com/jimhen3ry/fluidsynth_old/commit/b1d83486620dab823b75f68af8923a209960e329 wasapi: extend device enumeration for 24/32-bit formats

https://github.com/jimhen3ry/fluidsynth_old/commit/97e6476e695518be38ff73e7017eb5421be4d608 docs: fluidsettings.xml formatting--only reflow, no substantive changes

https://github.com/jimhen3ry/fluidsynth_old/commit/dd702455d6149773a3a38743b19757c1f4d8d930 docs: update audio.sample-format and WASAPI notes

Files changed

  • Synth/core:

    • include/fluidsynth/synth.h
    • src/synth/fluid_synth.*
    • src/synth/fluid_synth_write_int.*
  • Drivers/WASAPI:

    • src/drivers/fluid_wasapi.cpp
    • src/drivers/fluid_audio_convert.*
    • src/drivers/fluid_adriver.*
  • Device enumeration:

    • src/fluid_wasapi_device_enumerate.c
  • src/CMakeLists.txt

  • Tests:

    • test/test_synth_render_s16.c
    • test/test_synth_render_s24.c
    • test/test_synth_render_s32.c
    • test/CMakeLists.txt
  • Docs:

    • doc/fluidsettings.xml
    • doc/usage/audio_driver.txt

Testing

Builds in Debug + Release (MSVC/VS) and fluidsynth -Q runs.

All synth render tests pass except: s16 identity test fails by 1 LSB in Debug only (Release passes).

The ⓉⒺⓈⓉ.mid file plays using the VintageDreamsWaves-v2.sf2 for all sample formats using the WASAPI driver in both shared and exclusive modes.

@jimhen3ry

Copy link
Copy Markdown
Contributor Author

Fixes #1737

@derselbst derselbst left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've completed a first commit by commit review. Very impressive! I need to play around with that test/test_synth_render_s16.c a bit (@spessasus you might want to check out that file, since you asked about the testing suite recently)

There's one commit I do not like so much: 97e6476 - Sry, but I have an ultra wide screen, and this causes 2/3 of my screen becoming unused. I prefer using dynamic line wraps, so it nicely adjusts to whatever window size my editor is using. So pls. revert / drop that commit.

Rest looks good for the moment. I still need to test this locally, to get a bit more familiar with the changes, and also fix the CI builds.

Comment thread test/test_synth_render_s16.c Outdated
@derselbst

Copy link
Copy Markdown
Member

I rewrote some commits, so that it now compiles successfully with every commit. The test_synth_render_s16 fails with the same output:

hash mismatch: EXPECTED=0x7FD92354 got=0xAE467A06
s16 stream mismatch vs reference buffer
first sample mismatch @2 (interleaved index): exp=1 got=0 delta=-1
mismatches=230 max_abs_delta=2

    diff stats:
     max_abs_delta = 2
     rms_err = 0.708487
     snr_db = 73.80

at commits a29ed22 and 831cba1. So that's looking good. I'll look through the CI builds to see what their test output and then make this test tolerant as necessary.

@derselbst

derselbst commented Feb 1, 2026

Copy link
Copy Markdown
Member

Getting an interesting compile error if OSAL is glib:

     4>D:\a\fluidsynth\fluidsynth\src\synth\fluid_synth_write_int.cpp(49,5): error C2664: 'gint g_atomic_int_exchange_and_add(volatile gint *,gint)': cannot convert argument 1 from 'fluid_atomic_uint_t *' to 'volatile gint *' [D:\a\fluidsynth\fluidsynth\build\src\libfluidsynth-OBJ.vcxproj]
       D:\a\fluidsynth\fluidsynth\src\synth\fluid_synth_write_int.cpp(49,5): message : Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast [D:\a\fluidsynth\fluidsynth\build\src\libfluidsynth-OBJ.vcxproj]
       D:/deps/include/glib-2.0\glib/gatomic.h(41,10): message : see declaration of 'g_atomic_int_exchange_and_add' [D:\a\fluidsynth\fluidsynth\build\src\libfluidsynth-OBJ.vcxproj]

Seems like fluid_synth_get_ticks and fluid_synth_add_ticks need to be moved back to the C world. I'll do that as well.

@derselbst derselbst added this to the 2.6 milestone Feb 1, 2026
@derselbst derselbst force-pushed the issue-1737 branch 3 times, most recently from 1da54e1 to 725ff7f Compare February 1, 2026 15:28
@derselbst

Copy link
Copy Markdown
Member

Ok, so I've fixed all compilation issues so far. The only remaining problem is the failing unit tests for the two mingw32 CI builds. Looks like some float rounding error problem. In trying to reproduce them (by adding more mingw builds which use an older version though), I'm seeing strange errors that I never saw before in any CI build: it complains about the SF2 file being corrupted. Looks like an unrelated and very rare regression in the soundfont loader. So for the moment, I'm done. I need to think about this...

@jimhen3ry

Copy link
Copy Markdown
Contributor Author

Thank you for your Herculean efforts to deal with all the compilation issues.

The s16 test is irksome because it is testing for bit identity against a pre-change snapshot. I was more surprised that it passed on the Release build with my configuration than I was with the failure on the Debug build. It is the sort of situation where close can be good enough. But that is a hard standard to put into a test.

Best wishes for figuring out the soundfont loader regression.

@derselbst

Copy link
Copy Markdown
Member

Smth. goes wrong during fseek(file, 252534, SEEK_CUR). The new position should be 464 + 252534, but it is: https://github.com/FluidSynth/fluidsynth/actions/runs/21570781035/job/62149490004#step:6:2462

I don't yet understand why. I wrote a simple test for it and it didn't break. I extended the test with all the freads and seeks the sfloader does on the test soundfont and it breaks.

The s16 test is irksome because it is testing for bit identity against a pre-change snapshot.

That s16 test actually turns out to be pretty stable, now that I've permitted an abs diff of two. The s24 and s32 tests cause all the trouble I'm currently dealing with: https://github.com/FluidSynth/fluidsynth/actions/runs/21564899911/job/62134706057#step:6:148

Sorry that I'm doing all that in this PR. I'll clean it up once I (give up or) understand what's going on. Anyway, enough for today.

@jimhen3ry

Copy link
Copy Markdown
Contributor Author

I don't have a mingw build environment. The following is my attempt at a dry lab analysis of your build log with the s24 and s32 test errors.

The failing job is MINGW32 (32-bit) with GCC 15.2.0. Both the tests and fluid_synth_write_int.cpp are built with -O2 and no FP-stabilizing flags (-fexcess-precision=standard, -mfpmath=sse, etc.).

The deltas (s24: 256 = 1<<8, s32: 8) look like a single-quantization-step rounding difference, consistent with x87/excess precision on 32-bit.

Quick proof: try adding -fexcess-precision=standard for mingw32 only; if needed try -msse2 -mfpmath=sse or (diagnostic) -ffloat-store.

If any of those fix it, we can decide whether to keep a mingw32-only flag or make the conversion code explicitly round/spill before int conversion.

Hope this helps.

@derselbst

Copy link
Copy Markdown
Member

The mingw32 builds indeed implicitly use an x87 FPU. Switching to SSE2 explicitly passes the test. -fexcess-precision and -ffloat-store make no change:

https://github.com/FluidSynth/fluidsynth/actions/runs/21604127754/job/62320667267
https://github.com/FluidSynth/fluidsynth/actions/runs/21604127754/job/62320667271

I need to think about whether using _M_IX86_FP macro to compile-time check for SSE2 vs x87 and use adjusted testdata, or whether to completely skip that test for x87 FPUs.

@jimhen3ry

jimhen3ry commented Feb 3, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for running the tests to identify that the issue is the use of an x87 FPU.

Skipping the s24 and s32 tests for x87 FPUs is certainly a possibility. It is easy and there probably is not much risk of a regression that would affect only x87 FPUs.

If you go that way, I think you want to use __i386__ (32-bit x86) and __SSE2__ (SSE2 enabled), which are more reliable for GCC/MinGW, in addition to or instead of _M_IX86_FP, which is more of an MSVC thing.

If you think it is worth some effort to keep the s24 and s32 tests for all builds, I could modify it to make it strict by default, but relax it only on x87. On x87 the comparison could be changed to:

  • pass if abs(delta) <= tolerance
  • report mismatch count + worst delta (so you still see if things drift)

For s32, you observed a delta of 8, so a tolerance of 8 is reasonable if it’s stable. Here is a possible modification for the s32 test:

#include <inttypes.h>  /* for PRId32 if you want */

/* ... */

static int abs_int(int x) { return x < 0 ? -x : x; }

int main(void)
{
    /* ... existing setup ... */

#if defined(__i386__) && !defined(__SSE2__)
    const int kTol = 8;         /* x87 tolerance for s32 */
    const int kMaxBad = 16;     /* allow a few borderline samples */
#else
    const int kTol = 0;         /* strict everywhere else */
    const int kMaxBad = 0;
#endif

    int bad = 0;
    int maxAbsDelta = 0;
    int worstIdx = -1;
    int worstDelta = 0;

    for (i = 0; i < 2 * len; ++i)
    {
        int delta = (int)(out_s32[i] - exp_s32[i]);
        int ad = abs_int(delta);

        if (ad > maxAbsDelta)
        {
            maxAbsDelta = ad;
            worstIdx = i;
            worstDelta = delta;
        }

        if (ad > kTol)
        {
            fprintf(stderr,
                    "s32 mismatch @%d: exp=%d got=%d delta=%d\n",
                    i, (int)exp_s32[i], (int)out_s32[i], delta);
            bad++;
            if (bad > kMaxBad)
            {
                fprintf(stderr, "Too many mismatches (bad=%d), maxAbsDelta=%d\n", bad, maxAbsDelta);
                TEST_ASSERT(0);
            }
        }
    }

#if defined(__i386__) && !defined(__SSE2__)
    if (bad > 0)
    {
        fprintf(stderr, "x87 tolerated mismatches: bad=%d, maxAbsDelta=%d at idx=%d (delta=%d)\n",
                bad, maxAbsDelta, worstIdx, worstDelta);
    }
#endif

    /* ... cleanup ... */
}

Let me know if you would like me to submit a commit with the s24 and s32 tests revised to include these x87 FPU special cases. I propose a tolerance of 256 for the s24 test. Is 16 a good limit be for the number of mismatches tolerated before reporting too many mismatches?

@derselbst

Copy link
Copy Markdown
Member

I'd be open to both approaches. I have reverted all the recent debug commits, I'll pursue that in a separate branch. If you have some time, you're welcome to make the tests more tolerant for x87.

@jimhen3ry

Copy link
Copy Markdown
Contributor Author

Based on the CI results of my first commit (f7e707b), I did further work. I believe my revisions now address the x87 FPU issues appropriately.

Based on my tests, I set the tolerance on the s32 test to a delta of 16, which is very small for a 32 bit sound level. I set the count tolerance to 512 based on an observed count of 414, roughly 10% of the sample frames.

e8e7f66 is the good commit.

@derselbst

derselbst commented Feb 8, 2026

Copy link
Copy Markdown
Member

I added a unit test for round_clip_to() and completely revised its implementation, since the original logic was not well-defined: 2147483646.0f cannot be represented as float and instead becomes 2147483648.0f, which is too big when being casted to int32.

@derselbst derselbst left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just had a final look into it and I think it's good enough to merge it now. Thanks for contributing!

@derselbst derselbst merged commit c2c3c50 into FluidSynth:master Feb 14, 2026
62 of 72 checks passed
@jimhen3ry

Copy link
Copy Markdown
Contributor Author

And thanks for all the work you did to push this over the finish line!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add 32-bit signed PCM (“32bits”) output support (synth API + WASAPI)

3 participants