From e535786266e56d6f93b2df5ee7e9428adf030078 Mon Sep 17 00:00:00 2001 From: paul-krug Date: Thu, 11 Jun 2026 17:49:28 +0200 Subject: [PATCH 1/4] Fix Tube::operator= dropping members; fix vtlGetTransferFunction options 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. --- src/VocalTractLabApi/VocalTractLabApi.cpp | 7 +++++-- src/VocalTractLabBackend/Tube.cpp | 6 ++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/VocalTractLabApi/VocalTractLabApi.cpp b/src/VocalTractLabApi/VocalTractLabApi.cpp index 70b907b..512fc73 100644 --- a/src/VocalTractLabApi/VocalTractLabApi.cpp +++ b/src/VocalTractLabApi/VocalTractLabApi.cpp @@ -788,11 +788,13 @@ int vtlGetTransferFunction(double* tractParams, int numSpectrumSamples, Transfer TlModel* tlModel = new TlModel(); - // Set the options + // Set the options. + // tfOpts is declared outside the if-block below, because it must + // still be alive when opts is dereferenced further down. + TransferFunctionOptions tfOpts; TlModel::Options tlOpts; if (opts == NULL) { - TransferFunctionOptions tfOpts; vtlGetDefaultTransferFunctionOptions(&tfOpts); opts = &tfOpts; } @@ -805,6 +807,7 @@ int vtlGetTransferFunction(double* tractParams, int numSpectrumSamples, Transfer tlOpts.piriformFossa = opts->piriformFossa; tlOpts.radiation = (TlModel::RadiationType)opts->radiationType; tlOpts.softWalls = opts->softWalls; + tlOpts.staticPressureDrops = opts->staticPressureDrops; tlModel->options = tlOpts; vocalTract->getTube(&tlModel->tube); diff --git a/src/VocalTractLabBackend/Tube.cpp b/src/VocalTractLabBackend/Tube.cpp index b6dfd24..14ba164 100644 --- a/src/VocalTractLabBackend/Tube.cpp +++ b/src/VocalTractLabBackend/Tube.cpp @@ -761,6 +761,12 @@ void Tube::operator=(const Tube &t) this->teethPosition_cm = t.teethPosition_cm; this->aspirationStrength_dB = t.aspirationStrength_dB; this->tongueTipSideElevation = t.tongueTipSideElevation; + + this->staticPartsInitialized = t.staticPartsInitialized; + this->subglottalCavityLength_cm = t.subglottalCavityLength_cm; + this->nasalCavityLength_cm = t.nasalCavityLength_cm; + this->piriformFossaLength_cm = t.piriformFossaLength_cm; + this->piriformFossaVolume_cm3 = t.piriformFossaVolume_cm3; } // **************************************************************************** From 9bfffdcee357c92d982fdc8737bbe44717c74c58 Mon Sep 17 00:00:00 2001 From: paul-krug Date: Thu, 11 Jun 2026 20:26:52 +0200 Subject: [PATCH 2/4] Add C-API functions exposing glottis, TDS, anatomy and geometry internals 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) --- include/VocalTractLabApi/VocalTractLabApi.h | 366 +++++++++ include/VocalTractLabBackend/TlModel.h | 5 + include/VocalTractLabBackend/Tube.h | 1 + include/VocalTractLabBackend/VocalTract.h | 12 +- src/VocalTractLabApi/VocalTractLabApi.cpp | 844 ++++++++++++++++++++ src/VocalTractLabApi/VocalTractLabApi.def | 4 + src/VocalTractLabBackend/TlModel.cpp | 38 + src/VocalTractLabBackend/Tube.cpp | 4 +- src/VocalTractLabBackend/VocalTract.cpp | 145 ++++ 9 files changed, 1413 insertions(+), 6 deletions(-) diff --git a/include/VocalTractLabApi/VocalTractLabApi.h b/include/VocalTractLabApi/VocalTractLabApi.h index 0dd2e92..fd7a9fa 100644 --- a/include/VocalTractLabApi/VocalTractLabApi.h +++ b/include/VocalTractLabApi/VocalTractLabApi.h @@ -694,6 +694,372 @@ C_EXPORT int vtlGesturalScoreToEmaAndMesh(const char *gestureFileName, const cha // **************************************************************************** +// **************************************************************************** +// Set control parameters on the current glottis model, call calcGeometry(), +// and return derived parameters and tube data. +// +// Parameters: +// o controlParams (in): Control parameter values (numGlottisParams elements). +// o derivedParams (out): Derived parameter values after calcGeometry(). +// Must have at least 8 elements for the geometric glottis. +// o numDerivedParams (out): Number of derived parameters written. +// o tubeLength_cm (out): Tube section lengths (2 elements). +// o tubeArea_cm2 (out): Tube section areas (2 elements). +// +// Function return value: +// 0: success. +// 1: The API has not been initialized. +// **************************************************************************** + +C_EXPORT int vtlGlottisCalcGeometry(double *controlParams, + double *derivedParams, + int *numDerivedParams, + double *tubeLength_cm, + double *tubeArea_cm2); + +// **************************************************************************** +// Perform one incTime step on the current glottis model, then call +// calcGeometry() and return the updated state. +// +// Parameters: +// o timeIncrement_s (in): Time step in seconds. +// o pressure_dPa (in): Pressure values (4 elements): +// [subglottal, lower_glottis, upper_glottis, supraglottal]. +// o controlParams (in): Control parameter values for this step. +// o derivedParams (out): Derived parameters after the step. +// o numDerivedParams (out): Number of derived parameters written. +// o tubeLength_cm (out): Tube section lengths (2 elements). +// o tubeArea_cm2 (out): Tube section areas (2 elements). +// +// Function return value: +// 0: success. +// 1: The API has not been initialized. +// **************************************************************************** + +C_EXPORT int vtlGlottisIncTime(double timeIncrement_s, double *pressure_dPa, + double *controlParams, double *derivedParams, + int *numDerivedParams, double *tubeLength_cm, + double *tubeArea_cm2); + +// **************************************************************************** +// Reset the motion state of the current glottis model (phase, time, filters). +// +// Function return value: +// 0: success. +// 1: The API has not been initialized. +// **************************************************************************** + +C_EXPORT int vtlGlottisResetMotion(); + +// **************************************************************************** +// Returns static parameter info for the current glottis model. +// The arrays must have at least numStaticParams elements. +// +// Parameters out: +// o names: Tab-separated parameter names (at least 10*numStaticParams chars). +// o paramMin, paramMax, paramStandard: Min, max, default values. +// o numStaticParams: Number of static parameters. +// +// Function return value: +// 0: success. +// 1: The API has not been initialized. +// **************************************************************************** + +C_EXPORT int vtlGetGlottisStaticParamInfo(char *names, double *paramMin, + double *paramMax, + double *paramStandard, + int *numStaticParams); + +// **************************************************************************** +// TDS (Time-Domain Simulation) Component Testing API +// **************************************************************************** + +// **************************************************************************** +// Set TDS options (e.g., disable noise sources for deterministic testing). +// +// Parameters: +// o generateNoiseSources (in): Enable/disable noise source generation. +// o turbulenceLosses (in): Consider fluid dynamic losses due to turbulence. +// o softWalls (in): Consider losses due to soft walls. +// o radiationFromSkin (in): Allow sound radiation from the skin. +// o piriformFossa (in): Include the piriform fossa. +// o innerLengthCorrections (in): Additional inductivities between sections. +// o transvelarCoupling (in): Sound transmission through the velum tissue. +// +// Function return value: +// 0: success. +// 1: The API has not been initialized. +// **************************************************************************** + +C_EXPORT int vtlTdsSetOptions( + bool generateNoiseSources, + bool turbulenceLosses, + bool softWalls, + bool radiationFromSkin, + bool piriformFossa, + bool innerLengthCorrections, + bool transvelarCoupling +); + +// **************************************************************************** +// Reset the TDS model motion state. +// +// Function return value: +// 0: success. +// 1: The API has not been initialized. +// **************************************************************************** + +C_EXPORT int vtlTdsResetMotion(); + +// **************************************************************************** +// Set the piriform fossa dimensions on the global tube object. +// This allows testing the TDS/Synthesizer with non-default fossa values +// through synthesis_add_tube (which normally uses the global tube's defaults). +// +// Parameters (in): +// o length_cm: Fossa length in cm. +// o volume_cm3: Fossa volume in cm^3. +// +// Function return value: +// 0: success. +// 1: The API has not been initialized. +// **************************************************************************** + +C_EXPORT int vtlSetFossaDims(double length_cm, double volume_cm3); + +// **************************************************************************** +// Set tube geometry on the TDS model and run one time step. +// Returns all internal state for component-level testing. +// +// Parameters (in): +// o tubeLength_cm: Pharynx+mouth section lengths (40 elements). +// o tubeArea_cm2: Pharynx+mouth section areas (40 elements). +// o tubeArticulator: Pharynx+mouth articulators (40 elements). +// o incisorPos_cm: Position of the incisors. +// o velumOpening_cm2: Naso-pharyngeal port area. +// o tongueTipSideElevation: TS3 parameter. +// o filtering: Apply area filtering (true/false). +// o pressureSourceSection: Section index for pressure source (-1 = none). +// o pressureSourceAmp: Pressure source amplitude in dPa. +// +// Parameters (out): +// o secArea: Area per section (93 elements). +// o secLength: Length per section (93 elements). +// o secR0: Left resistance per section (93 elements). +// o secR1: Right resistance per section (93 elements). +// o secL: Inductance per section (93 elements). +// o secC: Capacitance per section (93 elements). +// o secD: D value per section (93 elements). +// o secE: E value per section (93 elements). +// o secAlpha: Alpha (wall vibration) per section (93 elements). +// o secBeta: Beta (wall vibration) per section (93 elements). +// o secPressure: Pressure per section (93 elements). +// o bcMagnitude: Branch current magnitude (97 elements). +// o mouthFlow: Radiated flow from mouth (scalar). +// o nostrilFlow: Radiated flow from nostrils (scalar). +// o skinFlow: Radiated flow from skin (scalar). +// +// Function return value: +// 0: success. +// 1: The API has not been initialized. +// **************************************************************************** + +C_EXPORT int vtlTdsSetTubeAndRun( + double *tubeLength_cm, double *tubeArea_cm2, int *tubeArticulator, + double incisorPos_cm, double velumOpening_cm2, + double tongueTipSideElevation, bool filtering, int pressureSourceSection, + double pressureSourceAmp, double *secArea, double *secLength, double *secR0, + double *secR1, double *secL, double *secC, double *secD, double *secE, + double *secAlpha, double *secBeta, double *secPressure, double *bcMagnitude, + double *mouthFlow, double *nostrilFlow, double *skinFlow); + +// **************************************************************************** + +// **************************************************************************** +// Returns all 93 tube sections (area, length, volume, wall properties, etc.) +// for the given vocal tract parameters. +// +// Function return value: +// 0: success. +// 1: The API has not been initialized. +// **************************************************************************** + +C_EXPORT int vtlTractToFullTube(double *tractParams, + double *tubeLength_cm, + double *tubeArea_cm2, + double *tubeVolume_cm3, + double *tubeWallMass_cgs, + double *tubeWallStiffness_cgs, + double *tubeWallResistance_cgs, + int *tubeArticulator, + double *incisorPos_cm, + double *tongueTipSideElevation, + double *velumOpening_cm2, + double *piriformFossaLength_cm, + double *piriformFossaVolume_cm3); + +// **************************************************************************** +// Returns intermediate TL model values (matrixProduct for all 93 sections, +// fossa input impedance, radiation impedances) at a given frequency index. +// Used for analysis and debugging of the TL model computation. +// +// Function return value: +// 0: success. +// 1: The API has not been initialized. +// 2: freqIndex out of range. +// **************************************************************************** + +C_EXPORT int vtlGetTLIntermediateValues( + double *tractParams, + int numSpectrumSamples, + TransferFunctionOptions *opts, + int freqIndex, + double *matrix_A_re, double *matrix_A_im, + double *matrix_B_re, double *matrix_B_im, + double *matrix_C_re, double *matrix_C_im, + double *matrix_D_re, double *matrix_D_im, + double *fossa_input_imp_re, double *fossa_input_imp_im, + double *nose_rad_imp_re, double *nose_rad_imp_im, + double *mouth_rad_imp_re, double *mouth_rad_imp_im); + +// **************************************************************************** +// Returns the 129 cross-section areas, positions, and articulators from the +// VocalTract model after calling calculateAll on the given tract parameters. +// NUM_CENTERLINE_POINTS = (1 << 7) + 1 = 129. +// +// Parameters: +// o tractParams (in): Is a vector of vocal tract parameters with +// numVocalTractParams elements. +// o crossSectionAreas (out): Is a vector of 129 cross-section areas in cm^2. +// o crossSectionPositions (out): Is a vector of 129 cross-section positions +// along the center line in cm. +// o crossSectionArticulators (out): Is a vector of 129 articulator indices +// (int cast of Tube::Articulator enum). +// +// Function return value: +// 0: success. +// 1: The API has not been initialized. +// **************************************************************************** + +C_EXPORT int vtlGetCrossSections(double *tractParams, + double *crossSectionAreas, + double *crossSectionPositions, + int *crossSectionArticulators); + +// **************************************************************************** +// Returns the upper and lower cross-sectional profiles at a specific +// centerline index for the given vocal tract parameters. +// +// Parameters: +// o tractParams (in): Is a vector of vocal tract parameters with +// numVocalTractParams elements. +// o centerlineIndex (in): Index along the centerline (0..128). +// o upperProfile (out): Is a vector of 96 (NUM_PROFILE_SAMPLES) doubles +// representing the upper profile. +// o lowerProfile (out): Is a vector of 96 (NUM_PROFILE_SAMPLES) doubles +// representing the lower profile. +// o centerlineInfo (out): Is a vector of 6 doubles: +// [point.x, point.y, normal.x, normal.y, area, pos]. +// +// Function return value: +// 0: success. +// 1: The API has not been initialized. +// 2: The centerline index is out of range. +// **************************************************************************** + +C_EXPORT int vtlGetProfiles(double *tractParams, int centerlineIndex, + double *upperProfile, double *lowerProfile, + double *centerlineInfo); + +// **************************************************************************** +// Returns all 129 centerline points (x, y, normal_x, normal_y, pos) after +// computing the vocal tract geometry for the given parameters. +// +// Parameters: +// o tractParams (in): Is a vector of vocal tract parameters with +// numVocalTractParams elements. +// o centerlineData (out): Is a vector of 129*5 = 645 doubles. +// For each point i (0..128): [x, y, normal_x, normal_y, pos]. +// +// Function return value: +// 0: success. +// 1: The API has not been initialized. +// **************************************************************************** + +C_EXPORT int vtlGetCenterline(double *tractParams, double *centerlineData); + +// **************************************************************************** +// Returns the 4 outlines (upper, lower, tongue, epiglottis) used for +// centerline computation, after calculating vocal tract geometry. +// +// Parameters: +// o tractParams (in): Vocal tract parameters (numVocalTractParams elements). +// o outlineData (out): Flat array for 4 outlines, each point = (x, y). +// Layout: [upper_n, upper_x0, upper_y0, ..., lower_n, lower_x0, ...] +// Max total size: 4 * (1 + 2*200) = 1604 doubles. +// +// Function return value: +// 0: success. +// 1: The API has not been initialized. +// **************************************************************************** + +C_EXPORT int vtlGetOutlines(double *tractParams, double *outlineData, int *outlineSizes); + +C_EXPORT int vtlGetTongueRibData(double *tractParams, double *ribData, int *numRibs); + +C_EXPORT int vtlGetTongueWidthBounds(double *tractParams, double *boundsData, int *numRibs); + +C_EXPORT int vtlGetSurfaceVertices(double *tractParams, int surfaceIndex, + double *vertexData, int *numRibs, int *numRibPoints); + +C_EXPORT int vtlGetCuts(double *tractParams, int centerlineIndex, + double *cutData, int *numCuts); + +// **************************************************************************** +// Apply anatomy parameters derived from age and gender to the loaded vocal +// tract. This calls AnatomyParams::calcFromAge(), restrictParams(), and +// setFor() on the currently loaded VocalTract. +// +// Parameters: +// o ageMonths (in): Age in months (minimum 12). +// o isMale (in): true for male, false for female. +// +// Return values: +// 0: success. +// 1: The API has not been initialized. +// **************************************************************************** + +C_EXPORT int vtlSetAnatomyFromAge(int ageMonths, bool isMale); + +// **************************************************************************** +// Get the 13 anatomy parameters from the currently loaded vocal tract. +// +// Parameters: +// o anatomyParams (out): Array of 13 doubles to receive the values. +// +// Return values: +// 0: success. +// 1: The API has not been initialized. +// **************************************************************************** + +C_EXPORT int vtlGetAnatomyParams(double *anatomyParams); + +// **************************************************************************** +// Set the 13 anatomy parameters on the currently loaded vocal tract. +// This calls AnatomyParams::restrictParams() and setFor(). +// +// Parameters: +// o anatomyParams (in): Array of 13 doubles with the anatomy values. +// +// Return values: +// 0: success. +// 1: The API has not been initialized. +// **************************************************************************** + +C_EXPORT int vtlSetAnatomyParams(double *anatomyParams); + +// **************************************************************************** + #ifdef __cplusplus } /* end extern "C" */ #endif diff --git a/include/VocalTractLabBackend/TlModel.h b/include/VocalTractLabBackend/TlModel.h index 825e31a..88ad913 100644 --- a/include/VocalTractLabBackend/TlModel.h +++ b/include/VocalTractLabBackend/TlModel.h @@ -84,6 +84,10 @@ class TlModel void getImpulseResponseWindow(Signal *window, int length); void getImpulseResponse(Signal *impulseResponse, int lengthExponent); + Matrix2x2 getMatrixProduct(int section, int freqIndex); + ComplexValue getFossaInputImpedance(int freqIndex); + ComplexValue getNoseRadiationImpedance(int freqIndex); + ComplexValue getMouthRadiationImpedance(int freqIndex); void getSpectrum(SpectrumType type, ComplexSignal *spectrum, int spectrumLength, int section); int getMostConstrictedSection(); @@ -121,6 +125,7 @@ class TlModel ComplexValue noseRadiationImpedance[MAX_NUM_FREQ]; ComplexValue lungTerminationImpedance[MAX_NUM_FREQ]; ComplexValue radiationCharacteristic[MAX_NUM_FREQ]; + ComplexValue fossaInputImpedanceArray[MAX_NUM_FREQ]; // ************************************************************************ diff --git a/include/VocalTractLabBackend/Tube.h b/include/VocalTractLabBackend/Tube.h index 6fc1f87..6055c23 100644 --- a/include/VocalTractLabBackend/Tube.h +++ b/include/VocalTractLabBackend/Tube.h @@ -178,6 +178,7 @@ class Tube double nasalCavityLength_cm; double piriformFossaLength_cm; double piriformFossaVolume_cm3; + double rawVelumOpening_cm2 = 0.0; // ************************************************************************** // Private functions. diff --git a/include/VocalTractLabBackend/VocalTract.h b/include/VocalTractLabBackend/VocalTract.h index 5120caf..d223082 100644 --- a/include/VocalTractLabBackend/VocalTract.h +++ b/include/VocalTractLabBackend/VocalTract.h @@ -441,6 +441,7 @@ class VocalTract void calcCrossSections(); void getCrossProfiles(Point2D P, Point2D v, double *upperProfile, double *lowerProfile, bool considerTongue, Tube::Articulator &articulator, bool debug = false); + int getRawCuts(Point2D P, Point2D v, double *cutData, int maxCuts); void insertUpperProfileLine(Point2D P0, Point2D P1, int surfaceIndex, double *upperProfile, int *upperProfileSurface); void insertLowerProfileLine(Point2D P0, Point2D P1, int surfaceIndex, @@ -481,15 +482,16 @@ class VocalTract /// Private data. // ************************************************************************** -private: - bool intersectionsPrepared[NUM_SURFACES]; // For the fast intersection method - bool hasStoredControlParams; - double storedControlParams[NUM_PARAMS]; - + // Outlines used for centerline computation (public for debug export). LineStrip2D upperOutline; LineStrip2D lowerOutline; LineStrip2D tongueOutline; LineStrip2D epiglottisOutline; + +private: + bool intersectionsPrepared[NUM_SURFACES]; // For the fast intersection method + bool hasStoredControlParams; + double storedControlParams[NUM_PARAMS]; }; // **************************************************************************** diff --git a/src/VocalTractLabApi/VocalTractLabApi.cpp b/src/VocalTractLabApi/VocalTractLabApi.cpp index 512fc73..426b92a 100644 --- a/src/VocalTractLabApi/VocalTractLabApi.cpp +++ b/src/VocalTractLabApi/VocalTractLabApi.cpp @@ -34,6 +34,7 @@ #include "VocalTractLabBackend/GesturalScore.h" #include "VocalTractLabBackend/Speaker.h" +#include "VocalTractLabBackend/AnatomyParams.h" #include "VocalTractLabBackend/TlModel.h" @@ -698,6 +699,76 @@ int vtlFastTractToTube(double *tractParams, } +// **************************************************************************** +// Returns all 93 tube sections (area, length, volume, wall properties, etc.) +// for the given vocal tract parameters. Unlike vtlTractToTube which only +// returns pharynx+mouth sections, this returns the full tube including +// trachea, glottis, nose, fossa, and sinus sections. +// +// Function return value: +// 0: success. +// 1: The API has not been initialized. +// **************************************************************************** + +int vtlTractToFullTube(double *tractParams, + double *tubeLength_cm, + double *tubeArea_cm2, + double *tubeVolume_cm3, + double *tubeWallMass_cgs, + double *tubeWallStiffness_cgs, + double *tubeWallResistance_cgs, + int *tubeArticulator, + double *incisorPos_cm, + double *tongueTipSideElevation, + double *velumOpening_cm2, + double *piriformFossaLength_cm, + double *piriformFossaVolume_cm3) +{ + if (!vtlApiInitialized) + { + printf("Error: The API has not been initialized.\n"); + return 1; + } + + int i; + for (i = 0; i < VocalTract::NUM_PARAMS; i++) + { + vocalTract->param[i].x = tractParams[i]; + } + + vocalTract->calculateAll(); + + Tube tube; + vocalTract->getTube(&tube); + tube.setGlottisArea(0.0); + + // Copy all 93 sections + for (i = 0; i < Tube::NUM_SECTIONS; i++) + { + Tube::Section *ts = tube.section[i]; + tubeLength_cm[i] = ts->length_cm; + tubeArea_cm2[i] = ts->area_cm2; + tubeVolume_cm3[i] = ts->volume_cm3; + tubeWallMass_cgs[i] = ts->wallMass_cgs; + tubeWallStiffness_cgs[i] = ts->wallStiffness_cgs; + tubeWallResistance_cgs[i] = ts->wallResistance_cgs; + tubeArticulator[i] = ts->articulator; + } + + *incisorPos_cm = tube.teethPosition_cm; + *tongueTipSideElevation = tube.tongueTipSideElevation; + *velumOpening_cm2 = tube.getVelumOpening_cm2(); + + // Get static tube dimensions (fossa length and volume) + double subglottalLength, nasalLength, fossaLength, fossaVolume; + tube.getStaticTubeDimensions(subglottalLength, nasalLength, fossaLength, fossaVolume); + + *piriformFossaLength_cm = fossaLength; + *piriformFossaVolume_cm3 = fossaVolume; + + return 0; +} + // **************************************************************************** // Returns the default options for the transfer function calculation. // @@ -2253,3 +2324,776 @@ int vtlGesturalScoreToEmaAndMesh(const char *gestureFileName, const char *filePa } // **************************************************************************** + +// **************************************************************************** +// Set control parameters on the current glottis model, call calcGeometry(), +// and return derived parameters and tube data. +// **************************************************************************** + +int vtlGlottisCalcGeometry(double *controlParams, double *derivedParams, + int *numDerivedParams, double *tubeLength_cm, + double *tubeArea_cm2) +{ + if (!vtlApiInitialized) + { + printf("Error: The API has not been initialized.\n"); + return 1; + } + + Glottis *g = glottis[selectedGlottis]; + int numCP = (int)g->controlParam.size(); + + // Set control parameters + for (int i = 0; i < numCP; i++) + { + g->controlParam[i].x = controlParams[i]; + } + + // Calculate geometry + g->calcGeometry(); + + // Return derived parameters + int numDP = (int)g->derivedParam.size(); + *numDerivedParams = numDP; + for (int i = 0; i < numDP; i++) + { + derivedParams[i] = g->derivedParam[i].x; + } + + // Return tube data + g->getTubeData(tubeLength_cm, tubeArea_cm2); + + return 0; +} + +// **************************************************************************** +// Perform one incTime step, then calcGeometry, and return updated state. +// **************************************************************************** + +int vtlGlottisIncTime(double timeIncrement_s, double *pressure_dPa, + double *controlParams, double *derivedParams, + int *numDerivedParams, double *tubeLength_cm, + double *tubeArea_cm2) +{ + if (!vtlApiInitialized) + { + printf("Error: The API has not been initialized.\n"); + return 1; + } + + Glottis *g = glottis[selectedGlottis]; + int numCP = (int)g->controlParam.size(); + + // Set control parameters + for (int i = 0; i < numCP; i++) + { + g->controlParam[i].x = controlParams[i]; + } + + // Advance time + g->incTime(timeIncrement_s, pressure_dPa); + + // Calculate geometry with updated state + g->calcGeometry(); + + // Return derived parameters + int numDP = (int)g->derivedParam.size(); + *numDerivedParams = numDP; + for (int i = 0; i < numDP; i++) + { + derivedParams[i] = g->derivedParam[i].x; + } + + // Return tube data + g->getTubeData(tubeLength_cm, tubeArea_cm2); + + return 0; +} + +// **************************************************************************** +// Reset the motion state of the current glottis model. +// **************************************************************************** + +int vtlGlottisResetMotion() +{ + if (!vtlApiInitialized) + { + printf("Error: The API has not been initialized.\n"); + return 1; + } + + glottis[selectedGlottis]->resetMotion(); + return 0; +} + +// **************************************************************************** +// Returns static parameter info for the current glottis model. +// **************************************************************************** + +int vtlGetGlottisStaticParamInfo(char *names, double *paramMin, + double *paramMax, double *paramStandard, + int *numStaticParams) +{ + if (!vtlApiInitialized) + { + printf("Error: The API has not been initialized.\n"); + return 1; + } + + Glottis *g = glottis[selectedGlottis]; + int numSP = (int)g->staticParam.size(); + *numStaticParams = numSP; + + strcpy(names, ""); + for (int i = 0; i < numSP; i++) + { + strcat(names, g->staticParam[i].name.c_str()); + if (i != numSP - 1) + { + strcat(names, "\t"); + } + paramMin[i] = g->staticParam[i].min; + paramMax[i] = g->staticParam[i].max; + paramStandard[i] = g->staticParam[i].neutral; + } + + return 0; +} + +// **************************************************************************** +// TDS API implementations +// **************************************************************************** + +int vtlTdsSetOptions(bool generateNoiseSources, bool turbulenceLosses, + bool softWalls, bool radiationFromSkin, bool piriformFossa, + bool innerLengthCorrections, bool transvelarCoupling) +{ + if (!vtlApiInitialized) + { + printf("Error: The API has not been initialized.\n"); + return 1; + } + + tdsModel->options.generateNoiseSources = generateNoiseSources; + tdsModel->options.turbulenceLosses = turbulenceLosses; + tdsModel->options.softWalls = softWalls; + tdsModel->options.radiationFromSkin = radiationFromSkin; + tdsModel->options.piriformFossa = piriformFossa; + tdsModel->options.innerLengthCorrections = innerLengthCorrections; + tdsModel->options.transvelarCoupling = transvelarCoupling; + return 0; +} + +int vtlTdsResetMotion() +{ + if (!vtlApiInitialized) + { + printf("Error: The API has not been initialized.\n"); + return 1; + } + + tdsModel->resetMotion(); + return 0; +} + +int vtlSetFossaDims(double length_cm, double volume_cm3) +{ + if (!vtlApiInitialized) + { + printf("Error: The API has not been initialized.\n"); + return 1; + } + + tube->initPiriformFossa(length_cm, volume_cm3); + return 0; +} + +int vtlTdsSetTubeAndRun(double *tubeLength_cm, double *tubeArea_cm2, + int *tubeArticulator, double incisorPos_cm, + double velumOpening_cm2, double tongueTipSideElevation, + bool filtering, int pressureSourceSection, + double pressureSourceAmp, double *secArea, + double *secLength, double *secR0, double *secR1, + double *secL, double *secC, double *secD, double *secE, + double *secAlpha, double *secBeta, double *secPressure, + double *bcMagnitude, double *mouthFlow, + double *nostrilFlow, double *skinFlow) +{ + if (!vtlApiInitialized) + { + printf("Error: The API has not been initialized.\n"); + return 1; + } + + // Set tube geometry + Tube::Articulator articulator[Tube::NUM_PHARYNX_MOUTH_SECTIONS]; + for (int i = 0; i < Tube::NUM_PHARYNX_MOUTH_SECTIONS; i++) + { + articulator[i] = (Tube::Articulator)tubeArticulator[i]; + } + + tube->setPharynxMouthGeometry(tubeLength_cm, tubeArea_cm2, articulator, + incisorPos_cm, tongueTipSideElevation); + tube->setVelumOpening(velumOpening_cm2); + + // Load into TDS model + tdsModel->setTube(tube, filtering); + + // Set pressure source + if (pressureSourceSection >= 0) + { + tdsModel->setPressureSource(pressureSourceAmp, pressureSourceSection); + } + else + { + tdsModel->pressureSourceSection = -1; + tdsModel->pressureSourceAmp = 0.0; + } + + // Run one time step + double mouth = 0.0, nostril = 0.0, skin = 0.0; + tdsModel->proceedTimeStep(mouth, nostril, skin); + + // Copy out all internal state + for (int i = 0; i < Tube::NUM_SECTIONS; i++) + { + secArea[i] = tdsModel->tubeSection[i].area; + secLength[i] = tdsModel->tubeSection[i].length; + secR0[i] = tdsModel->tubeSection[i].R[0]; + secR1[i] = tdsModel->tubeSection[i].R[1]; + secL[i] = tdsModel->tubeSection[i].L; + secC[i] = tdsModel->tubeSection[i].C; + secD[i] = tdsModel->tubeSection[i].D; + secE[i] = tdsModel->tubeSection[i].E; + secAlpha[i] = tdsModel->tubeSection[i].alpha; + secBeta[i] = tdsModel->tubeSection[i].beta; + secPressure[i] = tdsModel->tubeSection[i].pressure; + } + + for (int i = 0; i < TdsModel::NUM_BRANCH_CURRENTS; i++) + { + bcMagnitude[i] = tdsModel->branchCurrent[i].magnitude; + } + + *mouthFlow = mouth; + *nostrilFlow = nostril; + *skinFlow = skin; + + return 0; +} + +// **************************************************************************** + +// **************************************************************************** + +int vtlGetTLIntermediateValues( + double *tractParams, int numSpectrumSamples, TransferFunctionOptions *opts, + int freqIndex, double *matrix_A_re, double *matrix_A_im, + double *matrix_B_re, double *matrix_B_im, double *matrix_C_re, + double *matrix_C_im, double *matrix_D_re, double *matrix_D_im, + double *fossa_input_imp_re, double *fossa_input_imp_im, + double *nose_rad_imp_re, double *nose_rad_imp_im, double *mouth_rad_imp_re, + double *mouth_rad_imp_im) +{ + + if (!vtlApiInitialized) + { + printf("Error: The API has not been initialized.\n"); + return 1; + } + + TlModel *tlModel = new TlModel(); + + // Set options + if (opts != NULL) + { + TlModel::Options tlOpts; + tlOpts.radiation = (TlModel::RadiationType)opts->radiationType; + tlOpts.boundaryLayer = opts->boundaryLayer; + tlOpts.heatConduction = opts->heatConduction; + tlOpts.softWalls = opts->softWalls; + tlOpts.hagenResistance = opts->hagenResistance; + tlOpts.innerLengthCorrections = opts->innerLengthCorrections; + tlOpts.lumpedElements = opts->lumpedElements; + tlOpts.paranasalSinuses = opts->paranasalSinuses; + tlOpts.piriformFossa = opts->piriformFossa; + tlOpts.staticPressureDrops = opts->staticPressureDrops; + tlModel->options = tlOpts; + } + + // Calculate Tube + for (int i = 0; i < VocalTract::NUM_PARAMS; i++) + { + vocalTract->param[i].x = tractParams[i]; + } + vocalTract->calculateAll(); + vocalTract->getTube(&tlModel->tube); + tlModel->tube.setGlottisArea(0.0); + + ComplexSignal dummySpectrum; + tlModel->getSpectrum(TlModel::FLOW_SOURCE_TF, &dummySpectrum, + numSpectrumSamples, Tube::FIRST_PHARYNX_SECTION); + + if (freqIndex < 0 || freqIndex >= (numSpectrumSamples / 2)) + { + delete tlModel; + return 2; + } + + for (int i = 0; i < Tube::NUM_SECTIONS; i++) + { + Matrix2x2 M = tlModel->getMatrixProduct(i, freqIndex); + matrix_A_re[i] = M.A.real(); + matrix_A_im[i] = M.A.imag(); + matrix_B_re[i] = M.B.real(); + matrix_B_im[i] = M.B.imag(); + matrix_C_re[i] = M.C.real(); + matrix_C_im[i] = M.C.imag(); + matrix_D_re[i] = M.D.real(); + matrix_D_im[i] = M.D.imag(); + } + + ComplexValue fcc = tlModel->getFossaInputImpedance(freqIndex); + *fossa_input_imp_re = fcc.real(); + *fossa_input_imp_im = fcc.imag(); + + ComplexValue ncc = tlModel->getNoseRadiationImpedance(freqIndex); + *nose_rad_imp_re = ncc.real(); + *nose_rad_imp_im = ncc.imag(); + + ComplexValue mcc = tlModel->getMouthRadiationImpedance(freqIndex); + *mouth_rad_imp_re = mcc.real(); + *mouth_rad_imp_im = mcc.imag(); + + delete tlModel; + return 0; +} + +// **************************************************************************** +// Returns the 129 cross-section areas, positions, and articulators from the +// VocalTract model after calling calculateAll on the given tract parameters. +// **************************************************************************** + +int vtlGetCrossSections(double *tractParams, double *crossSectionAreas, + double *crossSectionPositions, + int *crossSectionArticulators) +{ + if (!vtlApiInitialized) + { + printf("Error: The API has not been initialized.\n"); + return 1; + } + + // Store the current control parameter values. + vocalTract->storeControlParams(); + + // Set the given vocal tract parameters. + int i; + for (i = 0; i < VocalTract::NUM_PARAMS; i++) + { + vocalTract->param[i].x = tractParams[i]; + } + + // Calculate the vocal tract shape. + vocalTract->calculateAll(); + + // Copy the cross-section data to the output arrays. + for (i = 0; i < VocalTract::NUM_CENTERLINE_POINTS; i++) + { + crossSectionAreas[i] = vocalTract->crossSection[i].area; + crossSectionPositions[i] = vocalTract->crossSection[i].pos; + crossSectionArticulators[i] = (int)vocalTract->crossSection[i].articulator; + } + + // Restore the previous control parameter values. + vocalTract->restoreControlParams(); + + return 0; +} + +// **************************************************************************** +// Returns the upper and lower cross-sectional profiles at a specific +// centerline index for the given vocal tract parameters. +// **************************************************************************** + +int vtlGetProfiles(double *tractParams, int centerlineIndex, + double *upperProfile, double *lowerProfile, + double *centerlineInfo) +{ + if (!vtlApiInitialized) + { + printf("Error: The API has not been initialized.\n"); + return 1; + } + + if (centerlineIndex < 0 || + centerlineIndex >= VocalTract::NUM_CENTERLINE_POINTS) + { + printf("Error: centerlineIndex %d is out of range [0, %d).\n", + centerlineIndex, VocalTract::NUM_CENTERLINE_POINTS); + return 2; + } + + // Store the current control parameter values. + vocalTract->storeControlParams(); + + // Set the given vocal tract parameters. + int i; + for (i = 0; i < VocalTract::NUM_PARAMS; i++) + { + vocalTract->param[i].x = tractParams[i]; + } + + // Calculate the vocal tract shape. + vocalTract->calculateAll(); + + // Get the cross profiles at the specified centerline index. + double upProfile[VocalTract::NUM_PROFILE_SAMPLES]; + double loProfile[VocalTract::NUM_PROFILE_SAMPLES]; + Tube::Articulator articulator; + + vocalTract->getCrossProfiles(vocalTract->centerLine[centerlineIndex].point, + vocalTract->centerLine[centerlineIndex].normal, + upProfile, loProfile, true, articulator); + + // Copy profile data to output arrays. + for (i = 0; i < VocalTract::NUM_PROFILE_SAMPLES; i++) + { + upperProfile[i] = upProfile[i]; + lowerProfile[i] = loProfile[i]; + } + + // Copy centerline info: point.x, point.y, normal.x, normal.y, area, pos + centerlineInfo[0] = vocalTract->centerLine[centerlineIndex].point.x; + centerlineInfo[1] = vocalTract->centerLine[centerlineIndex].point.y; + centerlineInfo[2] = vocalTract->centerLine[centerlineIndex].normal.x; + centerlineInfo[3] = vocalTract->centerLine[centerlineIndex].normal.y; + centerlineInfo[4] = vocalTract->crossSection[centerlineIndex].area; + centerlineInfo[5] = vocalTract->crossSection[centerlineIndex].pos; + + // Restore the previous control parameter values. + vocalTract->restoreControlParams(); + + return 0; +} + +// **************************************************************************** +// Returns all 129 centerline points after computing vocal tract geometry. +// **************************************************************************** + +int vtlGetCenterline(double *tractParams, double *centerlineData) +{ + if (!vtlApiInitialized) + { + printf("Error: The API has not been initialized.\n"); + return 1; + } + + // Store the current control parameter values. + vocalTract->storeControlParams(); + + // Set the given vocal tract parameters. + int i; + for (i = 0; i < VocalTract::NUM_PARAMS; i++) + { + vocalTract->param[i].x = tractParams[i]; + } + + // Calculate the vocal tract shape. + vocalTract->calculateAll(); + + // Copy all 129 centerline points: x, y, normal_x, normal_y, pos + for (i = 0; i < VocalTract::NUM_CENTERLINE_POINTS; i++) + { + centerlineData[i * 5 + 0] = vocalTract->centerLine[i].point.x; + centerlineData[i * 5 + 1] = vocalTract->centerLine[i].point.y; + centerlineData[i * 5 + 2] = vocalTract->centerLine[i].normal.x; + centerlineData[i * 5 + 3] = vocalTract->centerLine[i].normal.y; + centerlineData[i * 5 + 4] = vocalTract->centerLine[i].pos; + } + + // Restore the previous control parameter values. + vocalTract->restoreControlParams(); + + return 0; +} + +// **************************************************************************** +// Returns the 4 outlines used for centerline computation. +// **************************************************************************** + +int vtlGetOutlines(double *tractParams, double *outlineData, int *outlineSizes) +{ + if (!vtlApiInitialized) + { + printf("Error: The API has not been initialized.\n"); + return 1; + } + + vocalTract->storeControlParams(); + + int i; + for (i = 0; i < VocalTract::NUM_PARAMS; i++) + { + vocalTract->param[i].x = tractParams[i]; + } + + vocalTract->calculateAll(); + + // Export 4 outlines: upper, lower, tongue, epiglottis + // Each outline is stored as n points of (x, y) pairs + int offset = 0; + auto exportOutline = [&](LineStrip2D &outline, int outlineIdx) + { + int n = outline.getNumPoints(); + outlineSizes[outlineIdx] = n; + for (int j = 0; j < n; j++) + { + outlineData[offset++] = outline.getControlPoint(j).x; + outlineData[offset++] = outline.getControlPoint(j).y; + } + }; + + exportOutline(vocalTract->upperOutline, 0); + exportOutline(vocalTract->lowerOutline, 1); + exportOutline(vocalTract->tongueOutline, 2); + exportOutline(vocalTract->epiglottisOutline, 3); + + vocalTract->restoreControlParams(); + + return 0; +} + +// **************************************************************************** +// Returns tongue rib data (point, normal, leftSideHeight, rightSideHeight). +// **************************************************************************** + +int vtlGetTongueRibData(double *tractParams, double *ribData, int *numRibs) +{ + if (!vtlApiInitialized) + { + printf("Error: The API has not been initialized.\n"); + return 1; + } + + vocalTract->storeControlParams(); + + int i; + for (i = 0; i < VocalTract::NUM_PARAMS; i++) + { + vocalTract->param[i].x = tractParams[i]; + } + + vocalTract->calculateAll(); + + *numRibs = VocalTract::NUM_TONGUE_RIBS; + for (i = 0; i < VocalTract::NUM_TONGUE_RIBS; i++) + { + ribData[i * 6 + 0] = vocalTract->tongueRib[i].point.x; + ribData[i * 6 + 1] = vocalTract->tongueRib[i].point.y; + ribData[i * 6 + 2] = vocalTract->tongueRib[i].normal.x; + ribData[i * 6 + 3] = vocalTract->tongueRib[i].normal.y; + ribData[i * 6 + 4] = vocalTract->tongueRib[i].leftSideHeight; + ribData[i * 6 + 5] = vocalTract->tongueRib[i].rightSideHeight; + } + + vocalTract->restoreControlParams(); + + return 0; +} + +// **************************************************************************** +// Returns tongue rib width bounds (minX, maxX) after getCrossProfiles + lowpass. +// boundsData: flat array of size numRibs * 2 (minX, maxX per rib). +// **************************************************************************** + +int vtlGetTongueWidthBounds(double *tractParams, double *boundsData, + int *numRibs) +{ + if (!vtlApiInitialized) + { + printf("Error: The API has not been initialized.\n"); + return 1; + } + + vocalTract->storeControlParams(); + + int i; + for (i = 0; i < VocalTract::NUM_PARAMS; i++) + { + vocalTract->param[i].x = tractParams[i]; + } + + vocalTract->calculateAll(); + + *numRibs = VocalTract::NUM_TONGUE_RIBS; + for (i = 0; i < VocalTract::NUM_TONGUE_RIBS; i++) + { + boundsData[i * 2 + 0] = vocalTract->tongueRib[i].minX; + boundsData[i * 2 + 1] = vocalTract->tongueRib[i].maxX; + } + + vocalTract->restoreControlParams(); + + return 0; +} + +// **************************************************************************** +// Export all vertex positions for a specific surface. +// vertexData: flat array of size numRibs * numRibPoints * 3 (x, y, z per vertex) +// **************************************************************************** + +int vtlGetSurfaceVertices(double *tractParams, int surfaceIndex, + double *vertexData, int *numRibs, int *numRibPoints) +{ + if (!vtlApiInitialized) + { + printf("Error: The API has not been initialized.\n"); + return 1; + } + if (surfaceIndex < 0 || surfaceIndex >= VocalTract::NUM_SURFACES) + { + printf("Error: Invalid surface index %d.\n", surfaceIndex); + return 2; + } + + vocalTract->storeControlParams(); + + int i; + for (i = 0; i < VocalTract::NUM_PARAMS; i++) + { + vocalTract->param[i].x = tractParams[i]; + } + + vocalTract->calculateAll(); + + Surface *s = &vocalTract->surface[surfaceIndex]; + *numRibs = s->numRibs; + *numRibPoints = s->numRibPoints; + + int idx = 0; + for (int r = 0; r < s->numRibs; r++) + { + for (int p = 0; p < s->numRibPoints; p++) + { + Point3D v = s->getVertex(r, p); + vertexData[idx++] = v.x; + vertexData[idx++] = v.y; + vertexData[idx++] = v.z; + } + } + + vocalTract->restoreControlParams(); + + return 0; +} + +// **************************************************************************** +// Returns raw triangle intersection cuts at a specific centerline index. +// cutData: flat array of size 2048 * 8 (P0.x, P0.y, P1.x, P1.y, +// n.x, n.y, globalSurfaceIndex, localSurfaceIndex per cut). +// numCuts: output, number of cuts found. +// **************************************************************************** + +int vtlGetCuts(double *tractParams, int centerlineIndex, double *cutData, + int *numCuts) +{ + if (!vtlApiInitialized) + { + printf("Error: The API has not been initialized.\n"); + return 1; + } + + if (centerlineIndex < 0 || + centerlineIndex >= VocalTract::NUM_CENTERLINE_POINTS) + { + printf("Error: centerlineIndex %d is out of range [0, %d).\n", + centerlineIndex, VocalTract::NUM_CENTERLINE_POINTS); + return 2; + } + + vocalTract->storeControlParams(); + + int i; + for (i = 0; i < VocalTract::NUM_PARAMS; i++) + { + vocalTract->param[i].x = tractParams[i]; + } + + vocalTract->calculateAll(); + + const int MAX_CUTS = 2048; + *numCuts = vocalTract->getRawCuts( + vocalTract->centerLine[centerlineIndex].point, + vocalTract->centerLine[centerlineIndex].normal, cutData, MAX_CUTS); + + vocalTract->restoreControlParams(); + + return 0; +} + +// **************************************************************************** +// Apply anatomy parameters derived from age and gender to the loaded vocal +// tract. +// **************************************************************************** + +int vtlSetAnatomyFromAge(int ageMonths, bool isMale) +{ + if (!vtlApiInitialized) + { + printf("Error: The API has not been initialized.\n"); + return 1; + } + + AnatomyParams ap; + ap.calcFromAge(ageMonths, isMale); + ap.restrictParams(); + ap.setFor(vocalTract); + + return 0; +} + +// **************************************************************************** +// Get the 13 anatomy parameters from the currently loaded vocal tract. +// **************************************************************************** + +int vtlGetAnatomyParams(double *anatomyParams) +{ + if (!vtlApiInitialized) + { + printf("Error: The API has not been initialized.\n"); + return 1; + } + + AnatomyParams ap; + ap.getFrom(vocalTract); + for (int i = 0; i < AnatomyParams::NUM_ANATOMY_PARAMS; i++) + { + anatomyParams[i] = ap.param[i].x; + } + + return 0; +} + +// **************************************************************************** +// Set the 13 anatomy parameters on the currently loaded vocal tract. +// **************************************************************************** + +int vtlSetAnatomyParams(double *anatomyParams) +{ + if (!vtlApiInitialized) + { + printf("Error: The API has not been initialized.\n"); + return 1; + } + + AnatomyParams ap; + for (int i = 0; i < AnatomyParams::NUM_ANATOMY_PARAMS; i++) + { + ap.param[i].x = anatomyParams[i]; + } + ap.restrictParams(); + ap.setFor(vocalTract); + + return 0; +} diff --git a/src/VocalTractLabApi/VocalTractLabApi.def b/src/VocalTractLabApi/VocalTractLabApi.def index 8d4f124..bb52a0f 100644 --- a/src/VocalTractLabApi/VocalTractLabApi.def +++ b/src/VocalTractLabApi/VocalTractLabApi.def @@ -29,3 +29,7 @@ vtlGesturalScoreToEma vtlGesturalScoreToEmaAndMesh vtlTractSequenceToEmaAndMesh vtlSaveSpeaker +vtlGlottisCalcGeometry +vtlGlottisIncTime +vtlGlottisResetMotion +vtlGetGlottisStaticParamInfo diff --git a/src/VocalTractLabBackend/TlModel.cpp b/src/VocalTractLabBackend/TlModel.cpp index 9f74ec5..cb2c982 100644 --- a/src/VocalTractLabBackend/TlModel.cpp +++ b/src/VocalTractLabBackend/TlModel.cpp @@ -592,6 +592,7 @@ void TlModel::prepareCalculations() matrixProduct[k][i] = K; } fossaInputImpedance = K.A / K.C; + fossaInputImpedanceArray[i] = fossaInputImpedance; // ************************************************************** // The matrix products of the subglottal system and pharynx. @@ -1504,3 +1505,40 @@ ComplexValue TlModel::getFlowSourceTF(int freqIndex, int section) // **************************************************************************** +// **************************************************************************** +/// Returns the matrix product of the tube sections up to the given section +/// at the given frequency index (for analysis / debugging). +// **************************************************************************** + +Matrix2x2 TlModel::getMatrixProduct(int section, int freqIndex) +{ + return matrixProduct[section][freqIndex]; +} + +// **************************************************************************** +/// Returns the input impedance of the piriform fossa at the given +/// frequency index. +// **************************************************************************** + +ComplexValue TlModel::getFossaInputImpedance(int freqIndex) +{ + return fossaInputImpedanceArray[freqIndex]; +} + +// **************************************************************************** +/// Returns the nose radiation impedance at the given frequency index. +// **************************************************************************** + +ComplexValue TlModel::getNoseRadiationImpedance(int freqIndex) +{ + return noseRadiationImpedance[freqIndex]; +} + +// **************************************************************************** +/// Returns the mouth radiation impedance at the given frequency index. +// **************************************************************************** + +ComplexValue TlModel::getMouthRadiationImpedance(int freqIndex) +{ + return mouthRadiationImpedance[freqIndex]; +} diff --git a/src/VocalTractLabBackend/Tube.cpp b/src/VocalTractLabBackend/Tube.cpp index 14ba164..73315ee 100644 --- a/src/VocalTractLabBackend/Tube.cpp +++ b/src/VocalTractLabBackend/Tube.cpp @@ -440,6 +440,7 @@ void Tube::setGlottisArea(const double area_cm2) void Tube::setVelumOpening(const double openingArea_cm2) { + rawVelumOpening_cm2 = openingArea_cm2; const int N = 4; // Only the first N-1 sections of the nose change their shape int i; Section *ts = NULL; @@ -554,7 +555,7 @@ void Tube::interpolate(const Tube *leftTube, const Tube *rightTube, const double double Tube::getVelumOpening_cm2() const { - return noseSection[0].area_cm2; + return rawVelumOpening_cm2; } @@ -767,6 +768,7 @@ void Tube::operator=(const Tube &t) this->nasalCavityLength_cm = t.nasalCavityLength_cm; this->piriformFossaLength_cm = t.piriformFossaLength_cm; this->piriformFossaVolume_cm3 = t.piriformFossaVolume_cm3; + this->rawVelumOpening_cm2 = t.rawVelumOpening_cm2; } // **************************************************************************** diff --git a/src/VocalTractLabBackend/VocalTract.cpp b/src/VocalTractLabBackend/VocalTract.cpp index c1e3e61..4dcb5d8 100644 --- a/src/VocalTractLabBackend/VocalTract.cpp +++ b/src/VocalTractLabBackend/VocalTract.cpp @@ -5726,6 +5726,151 @@ void VocalTract::calcCrossSections() } +// **************************************************************************** +/// Returns raw triangle intersection cuts for a cross-section defined by +/// point P and normal vector v. Each cut is stored as 8 doubles: +/// [P0.x, P0.y, P1.x, P1.y, n.x, n.y, globalSurfaceIndex, localSurfaceIndex] +/// Returns the number of cuts found. +// **************************************************************************** + +int VocalTract::getRawCuts(Point2D P, Point2D v, double *cutData, int maxCuts) +{ + const int NUM_PROFILE_SURFACES = 10; + + int i, k; + Surface *s; + Point2D P0, P1, n; + int globalIndex; + + int profileSurfaceIndex[NUM_PROFILE_SURFACES] = + { + UPPER_COVER, UPPER_TEETH, UPPER_LIP, UVULA, + LOWER_COVER, LOWER_TEETH, LOWER_LIP, EPIGLOTTIS, + LEFT_COVER, RADIATION + }; + + const int MAX_LIST_ENTRIES = 1024; + int indexList[MAX_LIST_ENTRIES]; + int numListEntries; + + int numCuts = 0; + + for (k = 0; k < NUM_PROFILE_SURFACES; k++) + { + globalIndex = profileSurfaceIndex[k]; + s = &surface[globalIndex]; + + if (makeFasterIntersections) + { + if (intersectionsPrepared[globalIndex] == false) + { + s->prepareIntersections(); + intersectionsPrepared[globalIndex] = true; + } + + s->prepareIntersection(P, v); + s->getTriangleList(indexList, numListEntries, MAX_LIST_ENTRIES); + + for (i = 0; i < numListEntries; i++) + { + if ((s->getTriangleIntersection(indexList[i], P0, P1, n)) && (numCuts < maxCuts) && + (P0.y < MAX_PROFILE_VALUE) && (P1.y < MAX_PROFILE_VALUE) && + (P1.y > MIN_PROFILE_VALUE) && (P1.y > MIN_PROFILE_VALUE)) + { + cutData[numCuts * 8 + 0] = P0.x; + cutData[numCuts * 8 + 1] = P0.y; + cutData[numCuts * 8 + 2] = P1.x; + cutData[numCuts * 8 + 3] = P1.y; + cutData[numCuts * 8 + 4] = n.x; + cutData[numCuts * 8 + 5] = n.y; + cutData[numCuts * 8 + 6] = (double)globalIndex; + cutData[numCuts * 8 + 7] = (double)k; + numCuts++; + } + } + } + else + { + s->prepareIntersection(P, v); + + for (i = 0; i < s->numTriangles; i++) + { + if ((s->getTriangleIntersection(i, P0, P1, n)) && (numCuts < maxCuts) && + (P0.y < MAX_PROFILE_VALUE) && (P1.y < MAX_PROFILE_VALUE) && + (P1.y > MIN_PROFILE_VALUE) && (P1.y > MIN_PROFILE_VALUE)) + { + cutData[numCuts * 8 + 0] = P0.x; + cutData[numCuts * 8 + 1] = P0.y; + cutData[numCuts * 8 + 2] = P1.x; + cutData[numCuts * 8 + 3] = P1.y; + cutData[numCuts * 8 + 4] = n.x; + cutData[numCuts * 8 + 5] = n.y; + cutData[numCuts * 8 + 6] = (double)globalIndex; + cutData[numCuts * 8 + 7] = (double)k; + numCuts++; + } + } + } + } + + // Also include TONGUE surface cuts (no P0.y/P1.y bounds, matching getCrossProfiles) + { + globalIndex = TONGUE; + s = &surface[TONGUE]; + + if (makeFasterIntersections) + { + if (intersectionsPrepared[TONGUE] == false) + { + s->prepareIntersections(); + intersectionsPrepared[TONGUE] = true; + } + + s->prepareIntersection(P, v); + s->getTriangleList(indexList, numListEntries, MAX_LIST_ENTRIES); + + for (i = 0; i < numListEntries; i++) + { + if ((s->getTriangleIntersection(indexList[i], P0, P1, n)) && (numCuts < maxCuts)) + { + cutData[numCuts * 8 + 0] = P0.x; + cutData[numCuts * 8 + 1] = P0.y; + cutData[numCuts * 8 + 2] = P1.x; + cutData[numCuts * 8 + 3] = P1.y; + cutData[numCuts * 8 + 4] = n.x; + cutData[numCuts * 8 + 5] = n.y; + cutData[numCuts * 8 + 6] = (double)globalIndex; + cutData[numCuts * 8 + 7] = (double)10; // localSurfaceIndex = 10 for tongue + numCuts++; + } + } + } + else + { + s->prepareIntersection(P, v); + + for (i = 0; i < s->numTriangles; i++) + { + if ((s->getTriangleIntersection(i, P0, P1, n)) && (numCuts < maxCuts)) + { + cutData[numCuts * 8 + 0] = P0.x; + cutData[numCuts * 8 + 1] = P0.y; + cutData[numCuts * 8 + 2] = P1.x; + cutData[numCuts * 8 + 3] = P1.y; + cutData[numCuts * 8 + 4] = n.x; + cutData[numCuts * 8 + 5] = n.y; + cutData[numCuts * 8 + 6] = (double)globalIndex; + cutData[numCuts * 8 + 7] = (double)10; + numCuts++; + } + } + } + } + + return numCuts; +} + + // **************************************************************************** /// Calculates the upper and lower profile of a cross-section defined by the /// Point P and normal vector v on the center line. From dbafdb8b998d8b1ced38e8ee70e255a546840feb Mon Sep 17 00:00:00 2001 From: paul-krug Date: Fri, 12 Jun 2026 19:11:48 +0200 Subject: [PATCH 3/4] Fix off-by-one array overruns found with AddressSanitizer 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. --- src/VocalTractLabBackend/AnatomyParams.cpp | 2 +- src/VocalTractLabBackend/VocalTract.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VocalTractLabBackend/AnatomyParams.cpp b/src/VocalTractLabBackend/AnatomyParams.cpp index da25fbe..7ee8460 100644 --- a/src/VocalTractLabBackend/AnatomyParams.cpp +++ b/src/VocalTractLabBackend/AnatomyParams.cpp @@ -1262,7 +1262,7 @@ void AnatomyParams::adaptArticulation(double *oldParams, double *newParams) // Scale the tongue side height linearly with the VT depth. ******* double depthScale = param[PALATE_DEPTH].x / origAnatomyParams.param[PALATE_DEPTH].x; - for (i=0; i < 4; i++) + for (i=0; i < VocalTract::NUM_PARAMS - VocalTract::TS1; i++) { newParams[VocalTract::TS1+i] = oldParams[VocalTract::TS1+i]*depthScale; } diff --git a/src/VocalTractLabBackend/VocalTract.cpp b/src/VocalTractLabBackend/VocalTract.cpp index 4dcb5d8..0bda084 100644 --- a/src/VocalTractLabBackend/VocalTract.cpp +++ b/src/VocalTractLabBackend/VocalTract.cpp @@ -4891,7 +4891,7 @@ void VocalTract::calcTongueRibs() lastInside = false; ok = false; - for (k=NUM_PROFILE_SAMPLES; k > N2; k--) + for (k=NUM_PROFILE_SAMPLES-1; k > N2; k--) { x = (double)k*PROFILE_SAMPLE_LENGTH - 0.5*PROFILE_LENGTH; From f8efca5daf58290ea22a1a1df06e5bd12799ff70 Mon Sep 17 00:00:00 2001 From: paul-krug Date: Sat, 13 Jun 2026 14:38:18 +0200 Subject: [PATCH 4/4] CI: fix Windows SDK target in test projects (MSB8036) 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. --- build/msw/VtlApiTests.vcxproj | 2 +- build/msw/VtlBackendTests.vcxproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/msw/VtlApiTests.vcxproj b/build/msw/VtlApiTests.vcxproj index a47e6d3..85e35d3 100644 --- a/build/msw/VtlApiTests.vcxproj +++ b/build/msw/VtlApiTests.vcxproj @@ -21,7 +21,7 @@ {3e9b609e-a2c5-4b62-b0d8-199758363deb} Win32Proj - 10.0.19041.0 + 10.0 Application v142 Unicode diff --git a/build/msw/VtlBackendTests.vcxproj b/build/msw/VtlBackendTests.vcxproj index 5b1be55..7198550 100644 --- a/build/msw/VtlBackendTests.vcxproj +++ b/build/msw/VtlBackendTests.vcxproj @@ -21,7 +21,7 @@ {b0eccf36-91d7-47ca-8f08-ab92c55d489b} Win32Proj - 10.0.19041.0 + 10.0 Application v142 Unicode