Skip to content

Extend C API with component-level access functions + memory bug fixes#59

Merged
timoataltavo merged 4 commits into
mainfrom
FEATURE_Extend_C_API
Jun 14, 2026
Merged

Extend C API with component-level access functions + memory bug fixes#59
timoataltavo merged 4 commits into
mainfrom
FEATURE_Extend_C_API

Conversation

@paul-krug

Copy link
Copy Markdown
Collaborator

Adds C-API surface for component-level access to the backend, plus
three pre-existing memory/correctness bug fixes found along the way.
Structured as three commits — easiest reviewed commit-by-commit.

Commit 1 — correctness fixes (small, ~20 lines)

  • Tube::operator= copied the 93 sections plus three scalars but
    silently dropped staticPartsInitialized, the subglottal/nasal
    cavity lengths and both piriform fossa dimensions, so a copied
    Tube could disagree with the original on static geometry.
  • vtlGetTransferFunction: the fallback TransferFunctionOptions
    was declared inside the if (opts == NULL) block but used through
    the opts pointer after the block ended (dangling pointer; under
    -O3 the stack slot was reused, corrupting the options). Also
    opts->staticPressureDrops was never copied into TlModel::Options,
    so that option was silently ignored.

Commit 2 — new C-API surface (the bulk; mechanical)

21 new vtl* functions exposing backend internals for component-level
use from language bindings (driving individual models step by step,
reading intermediate values) without full synthesis runs:

  • Glottis: vtlGlottisCalcGeometry, vtlGlottisIncTime,
    vtlGlottisResetMotion, vtlGetGlottisStaticParamInfo (also added
    to the Windows export table).
  • Time-domain simulation on a caller-supplied tube:
    vtlTdsSetTubeAndRun, vtlTdsSetOptions, vtlTdsResetMotion.
  • Anatomy scaling: vtlGetAnatomyParams, vtlSetAnatomyParams,
    vtlSetAnatomyFromAge, vtlSetFossaDims.
  • Geometry export: vtlGetSurfaceVertices, vtlGetCenterline,
    vtlGetOutlines, vtlGetCrossSections, vtlGetCuts,
    vtlGetProfiles, vtlGetTongueRibData, vtlGetTongueWidthBounds,
    vtlTractToFullTube.
  • TL introspection: vtlGetTLIntermediateValues, backed by new
    read-only TlModel accessors (getMatrixProduct,
    getFossaInputImpedance, getNose-/getMouthRadiationImpedance).

Supporting changes: VocalTract::getRawCuts(), the outline line
strips made public, 4 entries in the Windows export table.

This commit also folds in one small correctness fix (bundled here
because it's consumed by the new vtlTractToFullTube):
Tube::getVelumOpening_cm2() returned noseSection[0].area_cm2 — the
nose-section area after shaping and the MIN_AREA_CM2 clamp —
instead of the velopharyngeal opening the caller requested. It now
returns that raw value, stored in a new rawVelumOpening_cm2 member
set in setVelumOpening() (and copied in operator=). The two agree
for any normally-open velum and differ only at/near a closed port —
e.g. a requested opening of 0.0 previously read back as 0.0001
(the clamp floor), now reads back as 0.0. Affected readers: the
velumOpening_cm2 output of vtlTractToTube / vtlFastTractToTube /
vtlTractToFullTube, tube interpolation, and the Synthesizer text
export.

Commit 3 — off-by-one array overruns found with AddressSanitizer

  • AnatomyParams::adaptArticulation() scaled four tongue-side
    params but the model has three (TS1..TS3); TS1+3 == NUM_PARAMS,
    so the loop read and wrote one element past the oldParams/
    newParams stack arrays per call. The stray write can crash the
    process later (malloc abort in ~VocalTract() during vtlClose()
    after using the anatomy API, depending on stack layout). Loop bound
    is now derived from the enum.
  • VocalTract::calcTongueRibs() right-edge scan started at
    k = NUM_PROFILE_SAMPLES, reading upperProfile/lowerProfile one
    element past the end (read-only; rejected by the INVALID guards in
    the normal case → no behavior change expected). Now starts at the
    last valid index.

Testing

Builds clean on macOS arm64 (cmake Release). VtlApiTests 19/19 and
VtlBackendTests 1/1 pass. Each commit compiles standalone; new API
paths exercised under AddressSanitizer.

Known pre-existing issue (NOT addressed here, for the record)

AddressSanitizer shows EmaPoint::name strings sharing buffers with
XML-parser strings freed at the end of VocalTract::init()
(XmlNode.cpp); using the anatomy API then makes vtlClose()
double-free, so a process can abort at exit (work/results unaffected).
Pre-dates this PR (reproduces on current main + these additions) and
lives in the XML/EMA ownership code — worth its own issue/fix.

…ons handling

Tube::operator= copied the 93 sections plus three scalars but silently
lost staticPartsInitialized, the subglottal/nasal cavity lengths and
both piriform fossa dimensions, so copies of a Tube could disagree
with the original on static geometry.

vtlGetTransferFunction had two issues: tfOpts was declared inside the
if (opts == NULL) block but used through the opts pointer after the
block ended (dangling pointer), and opts->staticPressureDrops was
never copied into TlModel::Options, so that option was silently
ignored.
…nals

Adds 21 vtl* API functions that expose more of the backend's internals
through the C API, so that component-level access (driving individual
models step by step, reading intermediate values) is possible from
language bindings without going through full synthesis runs:

- Glottis models: vtlGlottisCalcGeometry, vtlGlottisIncTime,
  vtlGlottisResetMotion, vtlGetGlottisStaticParamInfo (also added to
  the Windows export table)
- Time-domain simulation: vtlTdsSetTubeAndRun, vtlTdsSetOptions,
  vtlTdsResetMotion
- Anatomy scaling: vtlGetAnatomyParams, vtlSetAnatomyParams,
  vtlSetAnatomyFromAge, vtlSetFossaDims
- Geometry / visualization: vtlGetSurfaceVertices, vtlGetCenterline,
  vtlGetOutlines, vtlGetCrossSections, vtlGetCuts, vtlGetProfiles,
  vtlGetTongueRibData, vtlGetTongueWidthBounds, vtlTractToFullTube
- Transmission-line introspection: vtlGetTLIntermediateValues, backed
  by new read-only TlModel accessors (getMatrixProduct,
  getFossaInputImpedance, getNose-/getMouthRadiationImpedance)

Supporting backend changes:
- VocalTract::getRawCuts() plus public outline line strips for the
  geometry export functions
- Tube: new rawVelumOpening_cm2 member; getVelumOpening_cm2() now
  returns the raw requested opening instead of noseSection[0].area_cm2
  (NOTE: behavior change for existing callers - see PR description)
AnatomyParams::adaptArticulation() scaled four tongue side parameters
(TS1+0 .. TS1+3), but the vocal tract model only has TS1..TS3;
TS1+3 == NUM_PARAMS, so the loop read and wrote one element past the
oldParams/newParams arrays (both double[NUM_PARAMS] on the caller's
stack in setFor). The stray write corrupts an adjacent stack slot,
which can crash the process later (observed as a malloc abort in
VocalTract::~VocalTract() during vtlClose() after using
vtlSetAnatomyFromAge / vtlSetAnatomyParams). The loop bound is now
derived from the enum. The three valid parameters are scaled exactly
as before, so computed results are unchanged.

VocalTract::calcTongueRibs() started its right-edge scan at
k = NUM_PROFILE_SAMPLES, reading upperProfile[i][k] and
lowerProfile[i][k] one element past the end (arrays have
NUM_PROFILE_SAMPLES entries). The scan now starts at the last valid
index. The out-of-bounds sample is rejected by the INVALID checks in
the normal case, so no behavior change is expected.
VtlBackendTests.vcxproj and VtlApiTests.vcxproj hard-pinned
WindowsTargetPlatformVersion 10.0.19041.0, which the windows-latest
runner image no longer ships, so MSBuild failed with MSB8036 at the
test-build steps. Use "10.0" (resolves to the latest installed SDK),
matching what VocalTractLabBackend.vcxproj and VocalTractLabApi.vcxproj
already use in this repo. Pre-existing CI rot, independent of the
C-API additions.
@paul-krug paul-krug requested a review from timoataltavo June 13, 2026 12:42
@timoataltavo timoataltavo merged commit 13488a6 into main Jun 14, 2026
10 checks passed
timoataltavo added a commit that referenced this pull request Jun 14, 2026
Follow-up to #59: export the new C-API functions in the Windows .def
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