code wiki / _hdl_build / nx_vox_source.nx

nx_vox_source.nx

buildroot/runtime/_hdl_build/nx_vox_source.nx

18028 B359 linesdepth 3pulls 3 transitivereach 2 importersview sourcekind library
docsdependenciesstructsconstsfunctions

about

nx_vox_source.nx -- a SELF-OSCILLATING physical voice source. Rung R2 of the bioacoustics lane. WHAT MAKES THIS DIFFERENT FROM EVERY OTHER AUDIO ORGAN IN THE TREE. nx_audio_bark, nx_tts_formant, nx_phoneme_synth and friends are all SOURCE-FILTER synthesisers: something generates a pulse train at a frequency you tell it, and a filter bank shapes it. The frequency is an input. Nothing oscillates on its own, so nothing can do what real animal larynges do -- jump registers, split into two pitches, break into chaos -- unless a human scripts each event by hand. That is why nx_audio_bark's header says "tune by ear": there is nothing to measure because there is no physics. Here the frequency is an OUTPUT. This is a mass-spring-damper model of two vocal folds, driven by nothing but a constant subglottal pressure, and it oscillates because of the aerodynamics: * Each fold is two coupled masses (lower m1, upper m2) -- the standard two-mass idealisation of the body-cover structure of a real fold. * The glottal areas are a1 = g0 + xL1 + xR1 and a2 = g0 + xL2 + xR2. * Bernoulli gives the driving pressure P1 = Ps * (1 - (amin/a1)^2) where amin is the narrowest point. When the glottis is CONVERGENT (a2 < a1) the narrowest point is downstream, pressure inside the glottis stays high, and the folds are pushed APART. When it is DIVERGENT (a1 < a2) the flow separates at the entry, P1 collapses to zero, and the springs pull the folds shut. * Because m2 lags m1, the glottis is convergent while opening and divergent while closing. Energy goes in during opening and not during closing. That asymmetry IS the oscillation -- it is why a larynx works, and it is the whole reason this file exists. EVERYTHING THE RULER MEASURES FALLS OUT OF THE PHYSICS, unscripted: * f0 emerges from mass and stiffness; it is never written down anywhere. * SUBHARMONICS appear when the two folds entrain at 2:1 instead of 1:1. * BIPHONATION appears when left and right folds are detuned enough to stop entraining and each runs at its own rate. This is why the model is left/right asymmetric rather than a single fold mirrored -- a symmetric model can never produce it. * CHAOS appears at high subglottal pressure, where the collision nonlinearity dominates. * NON-REPETITION comes from turbulent pressure jitter, so no two cycles are ever identical. A dog habituates to a sample loop precisely because a loop cannot do this. The claim "these phenomena are emergent" is not an assertion in a comment -- nx_vox_source_gate SWEEPS asymmetry and pressure and shows the regimes appearing in the order the physics predicts, as measured by nx_bioacoustic_bench, which was built and frozen BEFORE this file existed. Integer fixed-point throughout (Q16), explicit Euler at an oversampled step. Deterministic: the same parameters give bit-identical audio, which is what makes a regression baseline meaningful. license_tier: ORIGINAL expect_exit: 0

dependencies 2 imports · 1 importers

nx_syscalls.nx nx_isqrt.nx nx_vox_source.nx nx_squeaker.nx

imports: nx_syscalls.nxnx_isqrt.nx

imported by: nx_squeaker.nx

structs

none

consts

45const VF_Q: i64 = 65536
46const VF_PI2: i64 = 411774 // 2*pi in Q16 -- same constant the rest of the audio family uses
49const VF_P_F0_L: i64 = 0 // Hz natural frequency of the LEFT fold
50const VF_P_F0_R: i64 = 1 // Hz natural frequency of the RIGHT fold (detune -> biphonation)
51const VF_P_ZETA: i64 = 2 // Q16 damping ratio
52const VF_P_KCOUP: i64 = 3 // Q16 spring coupling between the lower and upper mass of one fold
53const VF_P_G0: i64 = 4 // Q16 rest half-gap; negative means the folds rest pressed together
54const VF_P_PS: i64 = 5 // Q16 subglottal pressure -- the ONLY energy input
55const VF_P_KCOLL: i64 = 6 // Q16 extra stiffness when the folds collide (the closure nonlinearity)
56const VF_P_F2RATIO: i64 = 7 // Q16 upper-mass natural frequency as a fraction of the lower
57const VF_P_DRIVE: i64 = 8 // Q16 aerodynamic force gain
58const VF_P_OVS: i64 = 9 // integration steps per audio sample
59const VF_P_SEED: i64 = 10 // jitter RNG seed
60const VF_P_JITTER: i64 = 11 // Q16 turbulent pressure fluctuation -- why no two cycles repeat
69const VF_P_G0_2: i64 = 12 // Q16
70const VF_P_N: i64 = 13
73const VF_S_XL1: i64 = 0
74const VF_S_VL1: i64 = 1
75const VF_S_XL2: i64 = 2
76const VF_S_VL2: i64 = 3
77const VF_S_XR1: i64 = 4
78const VF_S_VR1: i64 = 5
79const VF_S_XR2: i64 = 6
80const VF_S_VR2: i64 = 7
81const VF_S_RNG: i64 = 8
82const VF_S_N: i64 = 9
98const VF_K_WL1: i64 = 0 // Q16 w*dt for the lower-left mass
99const VF_K_WL2: i64 = 1
100const VF_K_WR1: i64 = 2
101const VF_K_WR2: i64 = 3
102const VF_K_RL1: i64 = 4 // Q16 2*zeta*w*dt
103const VF_K_RL2: i64 = 5
104const VF_K_RR1: i64 = 6
105const VF_K_RR2: i64 = 7
106const VF_K_N: i64 = 8
107const VF_XCLAMP: i64 = 16 * 65536 // +-16.0 displacement; a divergent solve is bounded, not loud
291const VF_D_CLOSURES: i64 = 0
292const VF_D_OPEN: i64 = 1
293const VF_D_CLAMP: i64 = 2
294const VF_D_N: i64 = 3

functions

109func vfold_defaults(p: *i64) -> i64
called by 1: sq_render
128func vfold_init(p: *i64, k: *i64, s: *i64, rate: i64) -> i64
152func vfold_rng(s: *i64) -> i64
called by 1: vfold_substep
163func vfold_substep(s: *i64, p: *i64, k: *i64) -> i64
264func vfold_render(out: *i64, n: i64, rate: i64, p: *i64, s: *i64, k: *i64) -> i64
called by 1: vfold_render_audio calls 1: vfold_substep
296func vfold_render_diag(out: *i64, n: i64, rate: i64, p: *i64, s: *i64, k: *i64, diag: *i64) -> i64
calls 1: vfold_substep
336func vfold_render_audio(out: *i64, n: i64, rate: i64, p: *i64, amp: i64) -> i64