Clean-room NIST SPHERE (and Shorten) to RIFF/WAV transcoder
Project description
desphere
Flatten a sphere. desphere is a clean-room, MIT-licensed, zero-dependency
transcoder from NIST SPHERE audio (the .sph format used by TIMIT, WSJ,
Switchboard, and friends) to plain RIFF/WAV. The CLI is sph2wav.
No tooling, no upload: there's a browser page that converts a
.sphto.waventirely client-side (WASM) — seeweb/. Prefersph2pipe/ffmpeg/soxif you have them; the page is for when you don't.
pip install desphere # pure Python, zero deps, works anywhere
pip install "desphere[fast]" # + optional Rust accelerator (big shorten files)
sph2wav utterance.sph # -> utterance.wav
sph2wav utterance.sph out.wav
sph2wav --info utterance.sph # inspect the SPHERE header
sph2wav utterance.sph - # WAV to stdout
from desphere import read_sphere, transcode, transcode_bytes
# Streaming API (the reference):
header, data = read_sphere("utterance.sph")
with open("utterance.wav", "wb") as f:
transcode(header, data, f)
# One-shot bytes API:
wav_bytes = transcode_bytes(open("utterance.sph", "rb").read())
Both APIs transparently use the Rust accelerator when installed (and fall
back to pure Python) — same bytes out either way. With desphere[fast], the
heavy kernels (shorten decode, G.711 expansion) run in Rust; pure-Python PCM is
already C-speed. The CLI passes a stray .wav through unchanged (with a warning),
so pointing it at an already-decoded file is harmless.
Why
libsndfile/soundfile cannot read SPHERE (especially shorten-compressed
SPHERE), and the tools that can — sph2pipe, the original shorten, FFmpeg's
decoder — are GPL/LGPL or otherwise awkwardly licensed. desphere is a
permissively licensed, dependency-free reimplementation.
Clean-room policy
desphere is implemented only from:
- The public NIST SPHERE format description (ASCII header, typed object fields).
- ITU-T G.711 for μ-law / a-law.
- Tony Robinson (1994), SHORTEN: Simple lossless and near-lossless waveform compression (CUED/F-INFENG/TR.156) for the shorten algorithm.
- Black-box testing: running
sox/ffmpeg/sph2pipeas binaries and comparing only their output — never reading their source.
Copyright protects source code, not file formats or algorithms (the idea/expression dichotomy), so consulting prose/tabular descriptions of a format — even third-party ones — is fine. The one rule we never break: we do not read GPL/LGPL source code, and we do not use writeups that embed verbatim GPL source. Authoritative non-source references (the TR, ITU specs, NIST docs) are the primary sources; prose writeups are at most a cross-check.
Supported matrix
The design principle is support the obvious lossless path first, and fail loudly on anything not yet validated — never emit a plausible-but-wrong WAV.
| Coding | Status |
|---|---|
pcm, 16-bit, 01/10 byte order |
✅ supported |
pcm, 32-bit, 01/10 byte order |
✅ supported |
pcm, multi-channel (interleaved) |
✅ supported (validate against an oracle for exotic files) |
pcm, 8-bit / 24-bit |
⛔ rejected (UnsupportedFormat) — sign/packing not yet validated |
ulaw / alaw (G.711, 8-bit) |
✅ supported (Phase B) — verified byte-exact vs ffmpeg on all 256 codes |
pcm,embedded-shorten-v2.00 (16-bit) |
✅ supported (Phase C) — byte-exact vs ffmpeg, mono & stereo |
ulaw,embedded-shorten (shorten type 8), incl. bitshift |
✅ supported (Phase C) — byte-exact vs sph2pipe (real CALLHOME) |
| shorten QLPC (LPC) blocks | ✅ supported (Phase C) — byte-exact vs shorten encoder + ffmpeg (orders 1–20) |
Adding a coding means registering a decoder in desphere/codecs.py; until then
the gate raises a precise error.
Test fixtures (the "zoo")
We don't depend on real corpus files to test: tools/make_fixtures.py
generates a zoo of SPHERE variants — every byte order, bit depth, and channel
count — and commits them under tests/fixtures/ with a manifest recording the
exact expected output. Harder codings (μ-law/a-law, eventually shorten) are
produced best-effort by driving sox/ffmpeg/shorten as black-box binaries.
python tools/make_fixtures.py # regenerate fixtures + manifest
pytest # validate everything
Rust, WASM, and consumers
The Python in src/desphere is the reference spec; rust/ is a
Rust port that reproduces it bit-for-bit (same fixtures), for speed and for
the web. It builds three ways from one crate:
- a Rust library — a Rust client (praatfan is a likely consumer) imports it via a path/git dependency;
- WASM (
wasm-pack build rust --target web --features wasm) — a WASM web app and theweb/page consume it; nothing is sent to a server; - a Python extension
desphere-native(pyo3/maturin) — the optionaldesphere[fast]accelerator above.
Because the consumers live in the same ucpresearch org, they depend on desphere
directly (path/git); publishing to crates.io / npm is not required (and is
skipped). PyPI is the one registry we target (the pure-Python desphere,
plus the optional desphere-native wheels).
Development
The virtualenv lives outside the (Syncthing-synced) repo and is symlinked in:
uv venv ~/local/scr/venvs/desphere
ln -s ~/local/scr/venvs/desphere .venv
uv pip install -e ".[dev]"
pytest
# Rust port:
cd rust && cargo test # byte-exact vs the same fixtures
Roadmap
- Phase A (done): SPHERE header + 16/32-bit PCM, lossless.
- Phase B (done): μ-law / a-law decode (ITU-T G.711).
- Phase C (done): embedded-shorten of 16-bit PCM, lossless μ-law (type 8,
including the non-linear BITSHIFT remap), and QLPC (LPC) blocks — validated
byte-for-byte vs ffmpeg / sph2pipe / the
shortenencoder on real and synthetic streams, mono and stereo. Remaining (low priority): 8/24-bit linear PCM. Seedocs/STATUS.mdanddocs/SHORTEN.md. - Eventually → Rust (for a Rust client / WASM, and for speed), mirroring
praatfan-core-clean's Python-first-then-Rust path. The Python implementation stays as the readable reference. Porting guidance:docs/RUST_PORT.md.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file desphere-0.1.0.tar.gz.
File metadata
- Download URL: desphere-0.1.0.tar.gz
- Upload date:
- Size: 108.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
97ddd72e489f348428f8c8fcc1effa77bf67dbdfce718325258abb1d252409b4
|
|
| MD5 |
67ebecb9513748315e8737b42ab09ca3
|
|
| BLAKE2b-256 |
e33c8720dbfb845284668a3d400977d00f3f373481f7ba7b52d80af982eb7d7a
|
Provenance
The following attestation bundles were made for desphere-0.1.0.tar.gz:
Publisher:
release.yml on ucpresearch/desphere
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
desphere-0.1.0.tar.gz -
Subject digest:
97ddd72e489f348428f8c8fcc1effa77bf67dbdfce718325258abb1d252409b4 - Sigstore transparency entry: 1990973378
- Sigstore integration time:
-
Permalink:
ucpresearch/desphere@a12223ab1e373024f9022bc796b927fb31507335 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ucpresearch
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a12223ab1e373024f9022bc796b927fb31507335 -
Trigger Event:
push
-
Statement type:
File details
Details for the file desphere-0.1.0-py3-none-any.whl.
File metadata
- Download URL: desphere-0.1.0-py3-none-any.whl
- Upload date:
- Size: 23.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
80ebcae1a2182d9d420037097424ad73d578aefa92581e7b57a1aefa20f782b0
|
|
| MD5 |
862f35f6122e19fe720623cb4df2eba2
|
|
| BLAKE2b-256 |
9c35d3b4b8746dd53f5ac0d7e969959031168baa164ef5c39e13a7cb94390f2e
|
Provenance
The following attestation bundles were made for desphere-0.1.0-py3-none-any.whl:
Publisher:
release.yml on ucpresearch/desphere
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
desphere-0.1.0-py3-none-any.whl -
Subject digest:
80ebcae1a2182d9d420037097424ad73d578aefa92581e7b57a1aefa20f782b0 - Sigstore transparency entry: 1990973494
- Sigstore integration time:
-
Permalink:
ucpresearch/desphere@a12223ab1e373024f9022bc796b927fb31507335 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ucpresearch
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a12223ab1e373024f9022bc796b927fb31507335 -
Trigger Event:
push
-
Statement type: