Skip to content

Preserve NumPy sample values across non-native byte orders - #500

Open
antonsoo wants to merge 2 commits into
bastibe:masterfrom
antonsoo:fix/non-native-array-byteorder
Open

antonsoo wants to merge 2 commits into
bastibe:masterfrom
antonsoo:fix/non-native-array-byteorder

Conversation

@antonsoo

@antonsoo antonsoo commented Oct 12, 2026 •

Copy link
Copy Markdown

NumPy arrays with a non-native byte order are passed to libsndfile as native samples, silently changing their values.

Convert write inputs to contiguous native storage in write(). Reject non-native read(out=...) buffers in _array_io, alongside the existing contiguity validation, before altering the output or moving the file position. Document the output-buffer restriction.

import io
import sys
import numpy as np
import soundfile as sf

order = ">" if sys.byteorder == "little" else "<"
x = np.array([1, 256, -2345], dtype=order + "i2")
file = io.BytesIO()
sf.write(file, x, 8000, format="WAV", subtype="PCM_16")
file.seek(0)
print(sf.read(file, dtype="int16")[0])

Before on a little-endian host: [256, 1, -10250]. After: [1, 256, -2345].

All 32 regressions fail on master and pass here: sixteen contiguous/strided non-native write cases and sixteen read-buffer rejection checks covering four sample dtypes, mono/stereo and fill settings. Recorded LJSpeech/AMI writes correct 123,616 sample values and match independent frame energies/spectra. All eight non-native output-buffer workflows reject before changing their buffers; all eight native controls remain byte-identical. Inputs remain unchanged.

Full native suite: 371 passed on CPython 3.12 and PyPy 3.10. Error lint and diff checks pass. Pyright retains the same two existing dirname(_libname) findings as master. Tested on little-endian Linux; no big-endian host was tested.

@bastibe bastibe left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thank you for your bug report. Were you actually using soundfile on a big endian system?

I don't think this is the right place to fix things, though. We should probably treat byte order like contiguous instead.

Comment thread soundfile.py Outdated

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

here we convert data to contiguous, we could also convert the byte order.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Moved native byte-order conversion to write(), alongside its contiguity conversion. Contiguous and strided inputs are covered; all sixteen write regressions pass on CPython and PyPy.

Comment thread soundfile.py
@@ -1421,6 +1421,12 @@ def _array_io(self, action, array, frames):
raise ValueError("Data must be C-contiguous")

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

if out is not contiguous, we just error out. We should do the same with byte order.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Non-native output arrays now raise ValueError before any samples are read. Tests verify the output bytes and file position stay unchanged, including fill settings.

@antonsoo

Copy link
Copy Markdown
Author

The reproduction used non-native-endian NumPy arrays on little-endian Linux; no big-endian host was tested.

Applied your suggested layout: write() now converts byte order alongside contiguity, and read(out=...) rejects non-native buffers before changing the buffer or advancing the file position. Removed the conversion/copyback recursion from _array_io and documented the output-buffer restriction.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants