pyllsm2 is a Python wrapper for libllsm2, focused on a clear,
NumPy-friendly API for speech analysis and synthesis.
The package now has two explicit API layers:
- Python-first API at
pyllsm2 - Raw CFFI API at
pyllsm2.raw
Most users should start with pyllsm2.
Detailed repository structure and API documentation are available in:
docs/README.zh-CN.mddocs/project-structure.zh-CN.mddocs/api-python.zh-CN.mddocs/api-raw.zh-CN.mddocs/development.zh-CN.md
To improve NumPy integration and reduce Python-side loops, this package adds:
src/pyllsm2/python_compat.c
This file exists only for Python-side integration helpers that bind high-level
feature containers to C-backed memory layouts. It is not part of upstream
libllsm2 core sources.
This repository intentionally modifies a small part of the bundled C sources to support direct NumPy-style memory sharing for high-level feature arrays.
Modified files:
src/pyllsm2/python_compat.cvendor/libllsm2/llsm.hvendor/libllsm2/container.cvendor/libllsm2/frame.c
Purpose of these modifications:
- make
Layer0Featuresharmonic arrays C-backed and directly viewable as NumPy arrays - make
Layer1Featuresscalar/vector arrays C-backed and directly viewable as NumPy arrays - remove reliance on Python-side auto-commit array wrappers for common ndarray workflows
- remove high-level legacy getter/setter style feature access in favor of direct ndarray properties
- keep chunk copy/free logic consistent when frame members are rebound to shared array storage
In the modified C/C header files, comments marked with pyllsm2 modification
identify the added compatibility code paths.
Internal audit note for src/pyllsm2/python_compat.c:
- retired legacy helper exports that only served the pre-ndarray compat path
- kept only helpers still used by the current high-level wrapper or raw regression tests
- canonicalized F0 / Rd / harmonic writes through shared ndarray-backed buffers
- stopped relying on the old
llsm_py_chunk_resample_linear_f0path because it no longer matches the post-refactor chunk ownership model
- Wraps the public
libllsm2APIs from:llsm.hdsputils.hllsmrt.hllsmutils.h
- Exposes selected
cigletAPIs used with LLSM2:qifftspec2envlfmodel_*ifdetector_*filterbank_*
pyin and gvps are intentionally not included in this package build.
Editable install:
pip install -e ./pyllsm2If your environment cannot create an isolated build environment:
pip install -e ./pyllsm2 --no-build-isolationBuild note: package build requires a libllsm2 source tree at one of:
vendor/libllsm2libllsm2../libllsm2
import pyllsm2
analysis_options = pyllsm2.AnalysisOptions()
analysis_options.thop = 128 / 16000.0
analysis_options.maxnhar = 120
analysis_options.maxnhar_e = 5
analysis_options.npsd = 128
synthesis_options = pyllsm2.SynthesisOptions(16000)
# f0 should come from an external F0 extractor such as librosa or parselmouth
layer0 = pyllsm2.analyze(analysis_options, x, 16000, f0)
layer1 = pyllsm2.to_layer1(layer0, 1024)
output = pyllsm2.synthesize(synthesis_options, layer1)
y = output.yThe Python-first workflow is intentionally split into two feature containers:
Layer0Features: harmonic-plus-noise features produced directly by analysisLayer1Features: source-filter style features derived from layer 0
Typical flow:
import pyllsm2
layer0 = pyllsm2.analyze(analysis_options, x, fs, f0)
layer1 = pyllsm2.to_layer1(layer0, 1024)
resynth0 = pyllsm2.synthesize(synthesis_options, layer0)
resynth1 = pyllsm2.synthesize(synthesis_options, layer1)The top-level pyllsm2 module is intentionally small and discoverable.
AnalysisOptionsSynthesisOptionsLayer0FeaturesLayer1FeaturesChunkCoderRTSynthBufferOutput
analyze(...)to_layer1(...)to_layer0(...)synthesize(...)analyze_chunk(...)synthesize_output(...)warp_frequency(...)spectral_mean(...)spectrum_from_envelope(...)harmonic_analysis(...)synthesize_harmonic_frame(...)qifft(...)spec2env(...)lfmodel_from_rd(...)lfmodel_spectrum(...)lfmodel_period(...)ifdetector_estimate(...)
Layer0Features is the main analysis result. Common operations include:
layer0.f0layer0.ampllayer0.phselayer0.nharnp.log(layer0.ampl + 1e-6)layer0.ampl = np.zeros((nfrm, max_nhar), dtype=np.float32)layer0.phasepropagate(...)layer0.phasesync_rps(...)layer0.resample_linear_f0(...)layer0.to_layer1(...)
Array shapes and axis meanings:
layer0.f0: shape(nfrm,); axis 0 is frame index over timelayer0.nhar: shape(nfrm,); axis 0 is frame index over timelayer0.ampl: shape(nfrm, max_nhar); axis 1 is harmonic indexlayer0.phse: shape(nfrm, max_nhar); axis 1 is harmonic index
Layer1Features is the derived source-filter representation. Common operations include:
layer1.f0layer1.rdlayer1.vtmagnlayer1.vsphselayer1.vsphse_lengthsnp.mean(layer1.vtmagn, axis=0)layer1.vsphse = np.zeros((nfrm, max_nhar), dtype=np.float32)layer1.pitch_shift(...)layer1.to_layer0()
Array shapes and axis meanings:
layer1.f0: shape(nfrm,); axis 0 is frame index over timelayer1.rd: shape(nfrm,); axis 0 is frame index over timelayer1.vtmagn: shape(nfrm, nspec); axis 1 is spectral-bin indexlayer1.vsphse: shape(nfrm, max_nhar); axis 1 is source-harmonic indexlayer1.vsphse_lengths: shape(nfrm,); axis 0 is frame index over time
Chunk remains available for advanced or low-level workflows, but it is no longer
the primary user-facing feature container.
Direct object-style usage is supported in normal workflows. Context-manager usage is also available when you want explicit resource cleanup.
When you do need Chunk, prefer layer-specific allocation and direct ndarray
properties instead of mixed member-style access:
Chunk.allocate_layer0(nfrm, fs=..., max_nhar=...)Chunk.allocate_layer1(nfrm, fs=..., nspec=..., max_nhar=...)chunk.f0,chunk.ampl,chunk.phse,chunk.nharchunk.rd,chunk.vtmagn,chunk.vsphse,chunk.vsphse_lengths
Low-level access is available under pyllsm2.raw.
Use this layer only if you need direct CFFI access to native pointers, raw
constants, or one-to-one mappings of libllsm2 symbols.
import pyllsm2
ffi = pyllsm2.raw.ffi
lib = pyllsm2.raw.lib
opt = lib.llsm_create_aoptions()
method = pyllsm2.raw.LLSM_AOPTION_HMPPExamples of what lives under pyllsm2.raw:
ffilibLLSM_*constantsllsm_*native symbolscontainer_attach(...)- pointer-copy helpers such as
copy_fp_ptr(...)
- Top-level exports are Python-first and intentionally avoid raw symbol spam.
- Top-level analysis flow is centered on
Layer0Features -> Layer1Features. - Legacy aliases such as
AOptions,SOptions,analyze_frames, andsynthesize_audioare no longer part of the public top-level API. - Raw
libllsm2access remains available, but is now clearly separated underpyllsm2.raw.
Tests are now grouped by intent inside tests/:
tests/unit/: lightweight unit tests for high-level ndarray-oriented APIs.tests/learning/: practical, example-like tests that replace the oldexamples/folder.tests/raw/: focused tests forpyllsm2.rawhelpers, constants, pointers, and interop.tests/integration/: functional/integration coverage, including ports oflibllsm2/test/*.
Generated audio from the learning and integration suites is written to tests/audio_outputs/.
Install test dependencies:
pip install -e ./pyllsm2[test] --no-build-isolationRun tests:
pytest tests -qpyllsm2 uses:
pyproject.tomlsetuptoolscffisrc/layout
pyllsm2 is licensed under GPL-3.0-or-later.
- Main license text:
LICENSE - Third-party notices:
THIRD_PARTY_NOTICES.md