[{"data":1,"prerenderedAt":50},["ShallowReactive",2],{"project-omx-dsp-building":3},{"page":4,"others":46},{"slug":5,"src":6,"title":7,"source":8,"toc":9,"html":45},"building","BUILDING.md","Building omx-dsp","https:\u002F\u002Fgithub.com\u002FFreeMixer\u002Fomx-dsp\u002Fblob\u002Ff5c279a980a76a9b2ca53df5a9000f9416cb6cc3\u002FBUILDING.md",[10,13,16,19,21,24,27,30,33,36,39,42],{"id":11,"title":12},"flavours","Flavours",{"id":14,"title":15},"gcc-12-2-on-arm64","GCC 12.2 on arm64",{"id":17,"title":18},"golden-digests-and-libm","Golden digests and libm",{"id":20,"title":20},"libmvec",{"id":22,"title":23},"rates-outside-the-declaration","Rates outside the declaration",{"id":25,"title":26},"the-engine-s-copies","The engine's copies",{"id":28,"title":29},"the-channel-strip-s-input-stage","The channel strip's input stage",{"id":31,"title":32},"room-analysis-and-the-alignment-fit","Room analysis and the alignment fit",{"id":34,"title":35},"the-eq-maths-openmixer-s-typescript-computes","The EQ maths openmixer's TypeScript computes",{"id":37,"title":38},"loudness","Loudness",{"id":40,"title":41},"the-definitions-and-the-limits","The definitions and the limits",{"id":43,"title":44},"packages","Packages","\u003Ch1 id=\"building-omx-dsp\">Building omx-dsp\u003C\u002Fh1>\n\u003Cp>Needs a C11 compiler, GNU make, binutils, awk, sed, grep and diffutils.\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>command\u003C\u002Fth>\n\u003Cth>does\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>build\u002Flibomxdsp.a\u003C\u002Fcode>, \u003Ccode>build\u002Flibomxdsp-contracts.a\u003C\u002Fcode> and \u003Ccode>build\u002Flibomxdsp-tsan.a\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make test\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the whole suite: every primitive's and kernel's oracle at every declared rate with contracts on, the negative, perturbation and thread arms, the writable-data and doc checks, and the golden digests\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make check-package\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>what the deb's and the RPM's check run: the suite, the negative, consumer and thread arms, every oracle and golden digest, the flavours and the libmvec oracles; the sabotage and perturbation arms and the source checks run in \u003Ccode>make test\u003C\u002Fcode> (\u003Ccode>tools\u002Fcheck-package-check.sh\u003C\u002Fcode>, part of \u003Ccode>make checks\u003C\u002Fcode>, holds the two lists together)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make test-fx\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the effect kernels' oracles and golden digests alone\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make consumer\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the stateful stages built the way a consumer builds them (release flags, \u003Ccode>-Werror\u003C\u002Fcode>, states on the stack that only their init or settle writes) and run\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make test-analysis\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the feedback detector's, the RTA ring and spectrum's and HRP's oracles (the HRP render and live core among them) at the nine oracle rates (32 to 192 kHz), their no-allocation arm, golden digests and one sabotage per kernel; the room analysis, Farina sweep and alignment fit against closed forms at the nine rates and against core's TypeScript (the parity table), and one sabotage per kernel that both must catch; alone\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make test-engine-kernels\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the golden digests of the sample-rate converter, the generators and the analyser's readings at the nine oracle rates, which the engine's own copies wrote, and a break in each that must move its own digests and no other's\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make install PREFIX=\u002Fusr LIBDIR=\u002Fusr\u002Flib64 DESTDIR=…\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>headers, the three archives and \u003Ccode>omxdsp.pc\u003C\u002Fcode>, \u003Ccode>omxdsp-contracts.pc\u003C\u002Fcode>, \u003Ccode>omxdsp-tsan.pc\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make flavours\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>each archive carries its flavour, and a contracts consumer reads a violation raised inside the compiled code only through the contracts archive\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make docs\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the API reference with doxygen\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make exports\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>build\u002Fomx-dsp-exports.json\u003C\u002Fcode>: every public function and macro with its signature, its body as normalised tokens, its distinctive literals, maths calls, inline kernels and \u003Ccode>@twin\u003C\u002Fcode> names; \u003Ccode>make install\u003C\u002Fcode> puts it in \u003Ccode>$(DATADIR)\u002Fomxdsp\u002F\u003C\u002Fcode> beside \u003Ccode>omx-dsp-copy-check\u003C\u002Fcode> in \u003Ccode>$(BINDIR)\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make copy-check\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>tools\u002Fomx-dsp-copy-check.sh --self-test\u003C\u002Fcode>: planted copies in a scratch consumer (an exact copy, a renamed one, a TypeScript twin, a literal copy, inline kernels) must be found, a legitimate caller, boilerplate, tests and fakes must pass, and a stale or reasonless allowlist entry must fail; then each rule is broken in turn and the self-test must go red (part of \u003Ccode>make test\u003C\u002Fcode>, which runs it alone before everything else, so no parallel job runs beside it)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make lint\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the doc check, the source scan, the log10f guard, tools\u002Fcontract-single-source.sh (no name omx-contract defines is defined again here), tools\u002Fface-conformance.sh (every instance face's resolve takes its kernel's contract controls by name, order and kind), tools\u002Ftravel-grid-check.sh (a travel's step reaches its maximum and default, a choice names its ids) and tools\u002Fclone-check.sh (a second copy of a body inside the library goes red); each checks its own sabotage\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make test-tsan\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the thread arm under ThreadSanitizer, where the toolchain has it\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make engine-kernels-identity OPENMIXER=&lt;checkout&gt;\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>renders those digests again through the copies OpenMixer's engine still carries (\u003Ccode>mix_src.h\u003C\u002Fcode>, \u003Ccode>mix_dsp.h\u003C\u002Fcode>), and checks that each of its sabotages goes red; a desk check, not run in CI\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make test-mix\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the mix module's golden digests at the nine oracle rates (\u003Ccode>test\u002Fgolden\u002Fmix.sha256\u003C\u002Fcode>, written by the engine's own copies), and a break in each kernel and in each of the bus stages' contract checks that must turn its arm red; alone\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make mix-engine-identity OPENMIXER=&lt;checkout&gt;\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>renders the mix digests again through each set of copies OpenMixer's engine still carries (the bus stages' \u003Ccode>omx_mix_strip\u003C\u002Fcode>, \u003Ccode>omx_bus_out_gain\u003C\u002Fcode> and \u003Ccode>omx_bus_out_stage\u003C\u002Fcode> in \u003Ccode>mix_dsp.h\u003C\u002Fcode>, under the same names as \u003Ccode>mix\u002Fomx_bus_stage.h\u003C\u002Fcode>), and checks that each of its sabotages goes red; a desk check, not run in CI\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make strip-input-identity OMX_PLUGINS=&lt;checkout&gt;\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>renders omx-plugins' channel strip's own input stage (\u003Ccode>plugins\u002Fomx-strip\u002Fomx_strip.h\u003C\u002Fcode>) and requires the trim face's golden digests, then its sabotage; a desk check, not run in CI\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make analysis-engine-identity OPENMIXER=&lt;checkout&gt;\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>builds the analysis golden against the engine's own \u003Ccode>mix_dsp.h\u003C\u002Fcode>, \u003Ccode>hrp_render.h\u003C\u002Fcode> and \u003Ccode>hrp_lv2.h\u003C\u002Fcode> and requires the committed digests, then its sabotage; a desk check, not run in CI\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make room-ts-golden OPENMIXER=&lt;checkout&gt;\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>runs core's room analysis and alignment fit from an OpenMixer checkout under node (22.6 or later) and requires the committed parity table \u003Ccode>test\u002Fanalysis\u002Ffixtures\u002Froom_ts.h\u003C\u002Fcode>; a desk check, not run in CI\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make eq-sabotage\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the EQ bank, head-filter and drawn-curve arms (\u003Ccode>eq-curve\u003C\u002Fcode>, \u003Ccode>eq-design-ts\u003C\u002Fcode>) against nine sabotaged copies of the headers; each must fail both arms (part of \u003Ccode>make test\u003C\u002Fcode>)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make bench-mixmatrix\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the mix matrix's cost, then a 64 x 32 matrix held to MIXMATRIX_PERIOD_SHARE of the period at the nine rates, and a budget nothing can meet must go red; a desk check, not run in CI, whose shared runners time a core the console does not run on\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>make golden-write\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>rewrites every kernel's \u003Ccode>test\u002Fgolden\u002F&lt;kernel&gt;.sha256\u003C\u002Fcode>; only in a commit that bumps the minor version or adds a kernel\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>\u003Ccode>make -jN test\u003C\u002Fcode> is supported. \u003Ccode>tools\u002Fkernels-gen.sh\u003C\u002Fcode> runs once, as the Make rule that produces\n\u003Ccode>build\u002Fkernels-suite.inc.c\u003C\u002Fcode> and \u003Ccode>build\u002Fkernels-perturb.inc.c\u003C\u002Fcode>; every target that reads them lists\nthem as a prerequisite and no recipe or script calls the generator into \u003Ccode>build\u002F\u003C\u002Fcode>. \u003Ccode>make test-fx\u003C\u002Fcode>\nis one target per kernel, instance, oracle, golden and perturbation script (\u003Ccode>fx-run-*\u003C\u002Fcode>), so \u003Ccode>-j\u003C\u002Fcode>\nruns them side by side; the set run and each assertion are the same as serially.\n\u003Ccode>make gen-once\u003C\u002Fcode> (\u003Ccode>test\u002Fgen-once.sh\u003C\u002Fcode>, part of \u003Ccode>make test\u003C\u002Fcode>) counts the generations under \u003Ccode>-j8\u003C\u002Fcode> and\nmust see exactly one; it then re-adds the generator call to a recipe in a scratch copy of the\nMakefile, which must turn it red.\u003C\u002Fp>\n\u003Cp>Consumers compile the kernels through \u003Ccode>pkg-config --cflags omxdsp\u003C\u002Fcode>, which carries\n\u003Ccode>-ffp-contract=off\u003C\u002Fcode>: the kernels are inline, and without it an architecture with fused\nmultiply-add would round differently from one without.\u003C\u002Fp>\n\u003Ch2 id=\"flavours\">Flavours\u003C\u002Fh2>\n\u003Cp>The compiled part of the library (the oversampler, the FBS detector, the room analysis, the Farina\nsweep and the alignment fit; everything else is inline in the headers) is built once per flavour\ninto \u003Ccode>build\u002F\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>libomxdsp.a\u003C\u002Fcode>: \u003Ccode>CFLAGS\u003C\u002Fcode> only, contracts compiled out.\u003C\u002Fli>\n\u003Cli>\u003Ccode>libomxdsp-contracts.a\u003C\u002Fcode>: objects in \u003Ccode>build\u002Fcontracts\u002F\u003C\u002Fcode>, with \u003Ccode>-DOMX_CONTRACTS\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Ccode>libomxdsp-tsan.a\u003C\u002Fcode>: objects in \u003Ccode>build\u002Ftsan-lib\u002F\u003C\u002Fcode>, with \u003Ccode>-DOMX_CONTRACTS -fsanitize=thread\u003C\u002Fcode>.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Each has a pkg-config file of the same name (\u003Ccode>omxdsp\u003C\u002Fcode>, \u003Ccode>omxdsp-contracts\u003C\u002Fcode>, \u003Ccode>omxdsp-tsan\u003C\u002Fcode>) whose\ncflags carry the flavour's flags, so the inline headers and the archive are always built the same\nway. The suite, the thread arm and the contract-checked kernel tests link \u003Ccode>libomxdsp-contracts.a\u003C\u002Fcode>,\n\u003Ccode>make test-tsan\u003C\u002Fcode> links \u003Ccode>libomxdsp-tsan.a\u003C\u002Fcode>, and the release-flag kernel tests link \u003Ccode>libomxdsp.a\u003C\u002Fcode>.\n\u003Ccode>make flavours\u003C\u002Fcode> (\u003Ccode>tools\u002Fflavour-check.sh\u003C\u002Fcode>, part of \u003Ccode>make test\u003C\u002Fcode>) checks each archive's symbols and\nbuilds \u003Ccode>test\u002Fomxdsp_flavour.c\u003C\u002Fcode>, a contracts consumer that feeds a NaN to the compiled oversampler:\nagainst \u003Ccode>libomxdsp-contracts.a\u003C\u002Fcode> it must find the violation in its own ledger, and against\n\u003Ccode>libomxdsp.a\u003C\u002Fcode> it must fail.\u003C\u002Fp>\n\u003Ch2 id=\"gcc-12-2-on-arm64\">GCC 12.2 on arm64\u003C\u002Fh2>\n\u003Cp>Debian bookworm's GCC 12.2 on arm64 (Raspberry Pi OS, Zynthian) crashes with an internal compiler\nerror in \u003Ccode>vect_transform_reduction\u003C\u002Fcode> on a loop that keeps a \u003Ccode>double\u003C\u002Fcode> running maximum or minimum with\n\u003Ccode>fmax\u003C\u002Fcode>\u002F\u003Ccode>fmin\u003C\u002Fcode> over values widened from \u003Ccode>float\u003C\u002Fcode>. Write such a reduction as a comparison instead,\n\u003Ccode>if (!(e &lt;= worst)) worst = e;\u003C\u002Fcode>, which also carries a NaN into the result. \u003Ccode>make lint\u003C\u002Fcode> refuses the\nshape on any host (\u003Ccode>tools\u002Freduction-check.sh\u003C\u002Fcode>), and CI builds the suite on bookworm arm64.\u003C\u002Fp>\n\u003Ch2 id=\"golden-digests-and-libm\">Golden digests and libm\u003C\u002Fh2>\n\u003Cp>A golden digest covers the kernel's output bit for bit, so it also covers every libm call the\nkernel makes per sample. A libm's \u003Ccode>log10f\u003C\u002Fcode> is not one function: glibc 2.36 (debian:bookworm)\nmisrounds 1.39% of the non-negative floats, 62 225 of them by 2 ulp, where glibc 2.41 (debian:trixie)\nand 2.43 (Fedora 44) round every one correctly, since 2.41 took \u003Ccode>log10f\u003C\u002Fcode> from CORE-MATH. The limiter's\n48 kHz digest (the render of FreeMixer\u002Fomx-dsp#21) read \u003Ccode>5fe47250…\u003C\u002Fcode> on bookworm against \u003Ccode>69a449ce…\u003C\u002Fcode>\non 2.41 and 2.43. The compiler played no part: the same binary flips with the glibc it runs on.\u003C\u002Fp>\n\u003Cp>Production calls the libm: \u003Ccode>omx_lin_to_db()\u003C\u002Fcode> is \u003Ccode>20·log10f(max(lin, 1e-9))\u003C\u002Fcode>, about 2 ns per call\nagainst 10 ns for a libm-free \u003Ccode>log10f\u003C\u002Fcode> on glibc 2.43. The goldens do not: \u003Ccode>omx_log10f()\u003C\u002Fcode> lives in\n\u003Ccode>test\u002Fsupport\u002Flog10f_cr.h\u003C\u002Fcode>, a correctly rounded \u003Ccode>log10\u003C\u002Fcode> in IEEE double arithmetic (it needs\n\u003Ccode>FLT_EVAL_METHOD == 0\u003C\u002Fcode> and \u003Ccode>-ffp-contract=off\u003C\u002Fcode>, which the Makefile's \u003Ccode>FPFLAGS\u003C\u002Fcode> carry), and every\neffect-kernel golden TU is compiled with \u003Ccode>-include test\u002Fsupport\u002Flog10f_subst.h\u003C\u002Fcode>, which defines \u003Ccode>log10f\u003C\u002Fcode> as\n\u003Ccode>omx_log10f\u003C\u002Fcode>. The kernels are static inline, so the substitution reaches them, and every kernel's\ngolden is an exact digest on every toolchain. Two guards hold it: \u003Ccode>golden.h\u003C\u002Fcode> refuses to compile a\ngolden without the substitution and the golden driver exits 2 when \u003Ccode>log10f\u003C\u002Fcode> there is not\n\u003Ccode>omx_log10f\u003C\u002Fcode>, and \u003Ccode>tools\u002Flog10f-guard.sh\u003C\u002Fcode> (part of \u003Ccode>make lint\u003C\u002Fcode>) fails when \u003Ccode>omx_log10f\u003C\u002Fcode> appears\nunder \u003Ccode>include\u002F\u003C\u002Fcode> or \u003Ccode>src\u002F\u003C\u002Fcode>. \u003Ccode>make check-log10f\u003C\u002Fcode> compares \u003Ccode>omx_log10f\u003C\u002Fcode> with the host libm's \u003Ccode>log10f\u003C\u002Fcode>\non all 2³¹ non-negative floats; against glibc 2.43 none differ. \u003Ccode>test\u002Fkernels\u002Funits.c\u003C\u002Fcode> tests it\nagainst fixed bit patterns, including \u003Ccode>0x0efeee7a\u003C\u002Fcode>, 7.8e-10 ulp from a float midpoint, which is\nanswered from its exact value. The analysis golden (\u003Ccode>test\u002Fanalysis\u002Fgolden.test.c\u003C\u002Fcode>), whose HRP\ncode calls \u003Ccode>log10f\u003C\u002Fcode> through the compiled headers, links with \u003Ccode>-Wl,--wrap=log10f\u003C\u002Fcode> and answers each\ncall with \u003Ccode>omx_log10f\u003C\u002Fcode>; it exits 2 if the wrap was never reached.\u003C\u002Fp>\n\u003Cp>On glibc older than 2.41 a production build may therefore differ from the golden by up to 2 ulp of\na dB value in the units that call \u003Ccode>log10f\u003C\u002Fcode>. That is accepted.\u003C\u002Fp>\n\u003Cp>The limiter's other per-sample call, \u003Ccode>powf\u003C\u002Fcode> in \u003Ccode>omx_db_to_lin()\u003C\u002Fcode>, is the same implementation in\nevery glibc since 2.28 and returned the same bits for equal arguments on 2.36, 2.41 and 2.43. A\nkernel that adds a per-sample libm call adds it to the RT-safe allowlist named in its \u003Ccode>@note\u003C\u002Fcode>, and\nchecks that the goldens agree across glibc versions, or uses a libm-free word.\u003C\u002Fp>\n\u003Ch2 id=\"libmvec\">libmvec\u003C\u002Fh2>\n\u003Cp>The drive's shaper (\u003Ccode>omx_drive_shape_block\u003C\u002Fcode>) and the dynamics gain curves (the loops of\n\u003Ccode>omx_dynamics_keyed\u003C\u002Fcode> and \u003Ccode>omx_dyn_flat_gain_block\u003C\u002Fcode>) are stateless per sample, so their \u003Ccode>tanhf\u003C\u002Fcode>,\n\u003Ccode>log10f\u003C\u002Fcode> and \u003Ccode>powf\u003C\u002Fcode> run four samples at a time through glibc's libmvec. Measured on a Pi 5 (glibc\n2.41, gcc 14, 256-sample blocks), libm against libmvec in ns per sample: tanhf 9.34 to 2.52,\nlog10f 8.08 to 1.87, expf 6.22 to 1.80, powf 11.74 to 7.22. \u003Ccode>include\u002Fomxdsp\u002Fomx_fastmath.h\u003C\u002Fcode> makes the\n\u003Ccode>#pragma omp declare simd notinbranch\u003C\u002Fcode> declarations, because glibc's own \u003Ccode>&lt;math.h&gt;\u003C\u002Fcode> makes them only under\n\u003Ccode>__FAST_MATH__\u003C\u002Fcode>, which this library never uses (\u003Ccode>-ffast-math\u003C\u002Fcode> sets flush-to-zero at load).\u003C\u002Fp>\n\u003Cp>The build decides, per function, by compiling and linking a probe (\u003Ccode>tools\u002Ffastmath-probe.sh\u003C\u002Fcode>): a function\ncounts only when the compiler turns a loop over it into a call of its \u003Ccode>_ZGV\u003C\u002Fcode> symbol and the program links with\n\u003Ccode>-lm -lmvec\u003C\u002Fcode>. The Makefile then adds \u003Ccode>-fopenmp-simd -fno-math-errno\u003C\u002Fcode> and\n\u003Ccode>-DOMX_FASTMATH_MVEC_&lt;F&gt;=1\u003C\u002Fcode> for each function found, and \u003Ccode>-lmvec\u003C\u002Fcode> to the link; \u003Ccode>omxdsp.pc\u003C\u002Fcode> carries the same in its\n\u003Ccode>Cflags\u003C\u002Fcode> and \u003Ccode>Libs\u003C\u002Fcode>, so a consumer that links the static library takes libmvec on exactly the targets where the\nlibrary was built with it (\u003Ccode>pkg-config --cflags --libs omxdsp\u003C\u002Fcode>). Where libmvec is absent (Debian 12 on arm64;\nglibc older than 2.35 on x86-64; glibc older than 2.40 for \u003Ccode>tanhf\u003C\u002Fcode> and \u003Ccode>powf\u003C\u002Fcode> on aarch64) the probe finds nothing, the\nflags are empty and every kernel calls the libm as before. \u003Ccode>make FASTMATH=off\u003C\u002Fcode> forces that path. \u003Ccode>-fopenmp-simd\u003C\u002Fcode>\nlinks no OpenMP runtime; \u003Ccode>-fno-math-errno\u003C\u002Fcode> lets the compiler treat the four functions as pure, and enables no\nexception. The trapping-math default is kept: the loops select with \u003Ccode>omx_fm_select()\u003C\u002Fcode> rather than \u003Ccode>?:\u003C\u002Fcode> on floats, so\nnothing has to be speculated for them to vectorise.\u003C\u002Fp>\n\u003Cp>The output through libmvec is not bit-identical to the libm's. Held by:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>make fastmath\u003C\u002Fcode>: the oracle (\u003Ccode>test\u002Ffx\u002Ffastmath.test.c\u003C\u002Fcode>), each function against the double-precision libm over\nthe range the library uses, with its specials, and a check (\u003Ccode>tools\u002Ffastmath-vec-check.sh\u003C\u002Fcode>) that every loop\nabove really calls the vector symbols;\u003C\u002Fli>\n\u003Cli>\u003Ccode>make fastmath-diff\u003C\u002Fcode>: every kernel that uses them rendered on the libm and on libmvec at every declared rate,\nthe largest sample difference in dB re full scale, failing above \u003Ccode>FM_DIFF_LIMIT_DBFS\u003C\u002Fcode> (-96);\u003C\u002Fli>\n\u003Cli>\u003Ccode>make fastmath-sabotage\u003C\u002Fcode>: the oracle red on a result moved by 5 ULP, the loop check red on a header without\nthe pragma, the probe empty for a toolchain with no libmvec, no pragma or no vector symbol;\u003C\u002Fli>\n\u003Cli>\u003Ccode>make fastmath-plain\u003C\u002Fcode>: the oracle, the drive and the dynamics again with \u003Ccode>FASTMATH=off\u003C\u002Fcode>.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>The golden digests stay on the libm: their translation units are built with the flags before the libmvec ones\n(\u003Ccode>CFLAGS_PLAIN\u003C\u002Fcode>), and the stage's contracts build (\u003Ccode>-DOMX_CONTRACTS\u003C\u002Fcode>) keeps its per-sample calls, so a digest or a\ncontract ledger does not depend on the glibc's libmvec.\u003C\u002Fp>\n\u003Ch2 id=\"rates-outside-the-declaration\">Rates outside the declaration\u003C\u002Fh2>\n\u003Cp>The console declares six rates (\u003Ccode>OMX_DECLARED_RATES\u003C\u002Fcode>), and the kernels' contracts refuse any other\nwith a \u003Ccode>rate-is-declared\u003C\u002Fcode> precondition. The dynamics, balance, rotor and limiter oracles also run at\n32, 64 and 128 kHz, the other rates of the oracle set (\u003Ccode>OMX_FX_ORACLE_RATES\u003C\u002Fcode> in\n\u003Ccode>test\u002Ffx\u002Ffx_rates.h\u003C\u002Fcode>), with contracts still on: there the \u003Ccode>rate-is-declared\u003C\u002Fcode> precondition is\nexpected, and every other law must hold. Their golden digest files carry all nine rates.\u003C\u002Fp>\n\u003Ch2 id=\"the-engine-s-copies\">The engine's copies\u003C\u002Fh2>\n\u003Cp>The sample-rate converter (\u003Ccode>omx_src.h\u003C\u002Fcode>), the generators (\u003Ccode>omx_gen.h\u003C\u002Fcode>) and the analyser's spectrum\nreadings and half-band decimator (\u003Ccode>analysis\u002Fomx_rta.h\u003C\u002Fcode>) moved here from OpenMixer's engine unchanged in\nbehaviour. Their oracles are suite arms (\u003Ccode>test\u002Fkernels\u002Fsrc.c\u003C\u002Fcode>, \u003Ccode>gen.c\u003C\u002Fcode>,\n\u003Ccode>rta.c\u003C\u002Fcode>); \u003Ccode>test\u002Fengine_kernels\u002Fgolden.test.c\u003C\u002Fcode> hashes their output per kernel at the nine oracle rates,\nand \u003Ccode>test\u002Fgolden\u002Fengine_kernels.sha256\u003C\u002Fcode> holds the digests the engine's own copies gave, written by\n\u003Ccode>tools\u002Fengine-kernels-identity.sh --write\u003C\u002Fcode>. \u003Ccode>make engine-kernels-identity OPENMIXER=&lt;checkout&gt;\u003C\u002Fcode>\ncompiles the program again with those copies in place of the three headers, against the\nomx-contract release the engine pins, and requires the same digests (set\n\u003Ccode>ENGINE_IDENTITY_INCLUDE\u003C\u002Fcode> to the include directory of the omx-dsp the engine pins, e.g. \u003Ccode>git archive &lt;its commit&gt; include\u003C\u002Fcode>, when this tree's other headers need a newer contract than the engine's); its self-test changes one line\nin each copy and requires a failure each time. It is a desk check and CI does not run it: the copies\nlive in OpenMixer's private repository. It ends when the engine builds from these headers instead\nof its copies.\u003C\u002Fp>\n\u003Cp>The engine spells the converter's design rolloff \u003Ccode>OMX_SRC_ROLLOFF\u003C\u002Fcode>, a double; omx-contract renders\nit as the float \u003Ccode>OMX_SRC_ROLLOFF\u003C\u002Fcode> and the double \u003Ccode>OMX_SRC_ROLLOFF_DOUBLE\u003C\u002Fcode>. A caller that wants the\nengine's coefficients passes \u003Ccode>OMX_SRC_ROLLOFF_DOUBLE\u003C\u002Fcode> to \u003Ccode>omx_src_design\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch2 id=\"the-channel-strip-s-input-stage\">The channel strip's input stage\u003C\u002Fh2>\n\u003Cp>omx-plugins' channel strip ran its input trim and the trim's ramp as arithmetic of its own. That\nstage is now two faces: \u003Ccode>fx\u002Fomx_trim_instance.h\u003C\u002Fcode> (the trim kernel's one control, the console's\nde-zippered head gain) and \u003Ccode>fx\u002Fomx_eq_instance.h\u003C\u002Fcode> with every band off (the HPF and the LPF, which\nthe console runs as sections of its channel EQ). \u003Ccode>test\u002Fgolden\u002Ftrim_instance.sha256\u003C\u002Fcode> was written by\n\u003Ccode>make strip-input-identity OMX_PLUGINS=&lt;checkout&gt;\u003C\u002Fcode> (\u003Ccode>tools\u002Fstrip-input-identity.sh\u003C\u002Fcode>) from the\nplugin's own \u003Ccode>omx_strip.h\u003C\u002Fcode>, and \u003Ccode>test\u002Ffx\u002Ftrim_instance_golden.test.c\u003C\u002Fcode> holds the two faces to it at\nevery declared rate. Run it again whenever either face or the strip's input stage changes; it ends\nwhen the strip is generated from these faces and holds no arithmetic of its own.\u003C\u002Fp>\n\u003Ch2 id=\"room-analysis-and-the-alignment-fit\">Room analysis and the alignment fit\u003C\u002Fh2>\n\u003Cp>OpenMixer's room analysis and Smart Alignment's fit moved here from TypeScript:\n\u003Ccode>analysis\u002Fomx_align_fit.h\u003C\u002Fcode> (the cross-spectrum accumulator, the\ndelay and polarity fit, the correction), \u003Ccode>analysis\u002Fomx_room.h\u003C\u002Fcode> (the H1 transfer and coherence,\nthe impulse response and its early reflections, RT60 per octave band, the mode finder and the\nmode-EQ proposal) and \u003Ccode>analysis\u002Fomx_sweep.h\u003C\u002Fcode> (the Farina sweep, its inverse and the\ndeconvolution), compiled in \u003Ccode>src\u002Fomx_align_fit.c\u003C\u002Fcode>, \u003Ccode>src\u002Fomx_room.c\u003C\u002Fcode> and \u003Ccode>src\u002Fomx_sweep.c\u003C\u002Fcode>. They\nrun on a control thread, never in the audio callback: every buffer and scratch array is the\ncaller's, sized as each function states, and nothing is allocated, locked or kept between calls.\nA measurement that cannot conclude returns a refusal code (\u003Ccode>enum omx_analysis_refusal\u003C\u002Fcode>), one per\nword of core's refusal lists.\u003C\u002Fp>\n\u003Cp>The numbers core declares for these readers and omx-contract does not yet carry (the frame\nfloor, the default and mode bands, the trust coherence, the analysis floor, the early-reflection\nhorizon and floor, the T20 span, the octave centres, the mode margin and position floor, the\nsweep's fade fraction) are the caller's to pass; this tree types none of them. The proposal's\nbounds are omx-contract's (\u003Ccode>OMX_FBS_MAX_NOTCH_DEPTH_DB\u003C\u002Fcode>, \u003Ccode>OMX_EQ_GAIN_RANGE_*\u003C\u002Fcode>,\n\u003Ccode>OMX_EQ_Q_RANGE_*\u003C\u002Fcode>).\u003C\u002Fp>\n\u003Cp>Two kinds of oracle hold them. \u003Ccode>test\u002Fanalysis\u002Falign_fit.test.c\u003C\u002Fcode>, \u003Ccode>room.test.c\u003C\u002Fcode> and\n\u003Ccode>sweep.test.c\u003C\u002Fcode> check closed forms at the nine oracle rates (a known delay, gain, reflection,\ndecay, set of resonances, sweep law) and count allocations. \u003Ccode>test\u002Fanalysis\u002Froom_ts.test.c\u003C\u002Fcode> holds\nevery result to what core's TypeScript computes on fixed inputs, from the parity table\n\u003Ccode>test\u002Fanalysis\u002Ffixtures\u002Froom_ts.h\u003C\u002Fcode>: integers exactly, reals within 1e-9 relative (measured gap\nabout 1e-12; V8's libm against the host's). \u003Ccode>tools\u002Froom-ts-golden.mjs\u003C\u002Fcode> wrote the table from an\nOpenMixer checkout, with omx-contract's TypeScript render from the pinned release; every input\nis built from an integer PRNG and exact arithmetic, so the C test rebuilds the same bits.\n\u003Ccode>make room-ts-golden OPENMIXER=&lt;checkout&gt;\u003C\u002Fcode> writes it again and requires the committed file; run\nit whenever core's TypeScript or these kernels change, until the TypeScript is deleted. \u003Ccode>test\u002Fanalysis\u002Froom-sabotage.sh\u003C\u002Fcode> breaks each kernel once in a\nscratch copy and requires its closed-form oracle and the parity test to fail.\u003C\u002Fp>\n\u003Ch2 id=\"the-eq-maths-openmixer-s-typescript-computes\">The EQ maths openmixer's TypeScript computes\u003C\u002Fh2>\n\u003Cp>openmixer designs its console EQ and draws its curve in TypeScript today. The C\nhere computes the same results, so the engine and the server can take them from one place:\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>TypeScript\u003C\u002Fth>\n\u003Cth>C\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>core \u003Ccode>rbjSection\u003C\u002Fcode>, \u003Ccode>rbjCoeffs\u003C\u002Fcode> (core \u003Ccode>eq.ts\u003C\u002Fcode>)\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>omx_eq_design\u003C\u002Fcode>, \u003Ccode>omx_eq_design_f\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>core \u003Ccode>bandwidthOctavesQ\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>omx_eq_bandwidth_q\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>core \u003Ccode>butterworthSlopeQs\u003C\u002Fcode>, web \u003Ccode>butterworthSectionQs\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>omx_eq_butterworth_qs\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>core \u003Ccode>passFilterMaxSections\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>OMX_EQ_PASS_MAX_SECTIONS\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>core \u003Ccode>complementSection\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>omx_eq_complement\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>core \u003Ccode>eqBandIsIdentity\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>omx_eq_band_is_identity\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>core \u003Ccode>eqCoeffs\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>omx_eq_bank_design\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>core \u003Ccode>passFilterCoeffs\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>omx_eq_pass_design\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>core \u003Ccode>sectionMagnitudeDb\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>omx_biquad_magnitude_db_d\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>core \u003Ccode>biquadMagnitudeDb\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>omx_eq_band_magnitude_db\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>core \u003Ccode>eqCurveDb\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>omx_eq_curve_db\u003C\u002Fcode> (its frequencies: \u003Ccode>omx_eq_curve_freqs\u003C\u002Fcode>)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>web \u003Ccode>magnitudeSumDb\u003C\u002Fcode>, \u003Ccode>seatY\u003C\u002Fcode>'s value (web \u003Ccode>eqCurveGeom.ts\u003C\u002Fcode>)\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>omx_eq_magnitude_sum_db\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>web \u003Ccode>eqStateBands\u003C\u002Fcode>, \u003Ccode>filterBands\u003C\u002Fcode>, \u003Ccode>passFilterBands\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>omx_eq_curve_bands\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>web \u003Ccode>eqStateCurveDb\u003C\u002Fcode>, \u003Ccode>compositeCurveDb\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>omx_eq_curve_bands\u003C\u002Fcode>, then \u003Ccode>omx_eq_curve_db\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>catalog \u003Ccode>octavesToQ\u003C\u002Fcode> (catalog \u003Ccode>eq.ts\u003C\u002Fcode>)\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>omx_eq_octaves_q\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>\u003Ccode>test\u002Fkernels\u002Ffixtures\u002Feq_design_ts.h\u003C\u002Fcode> is what that TypeScript computes on fixed inputs at the nine\noracle rates, written by \u003Ccode>tools\u002Fts-golden.mjs design &lt;openmixer&gt; &lt;contract-ts&gt;\u003C\u002Fcode> (Node 22.7 or later;\n\u003Ccode>&lt;contract-ts&gt;\u003C\u002Fcode> is omx-contract's TypeScript render, \u003Ccode>omx-contract render --target ts --out &lt;dir&gt;\u003C\u002Fcode>\nfrom a checkout of the installed release). The \u003Ccode>eq-design-ts\u003C\u002Fcode> arm holds the C to it: a coefficient, a Q or\na frequency of the drawn grid within 4 ulp, a magnitude within 1e-9 dB, the bank's order, count and\nparked sections exactly. Two families of coefficient pass 4 ulp, both a cancellation that turns one\nulp of V8's \u003Ccode>exp\u003C\u002Fcode> or \u003Ccode>cos\u003C\u002Fcode> into many (a matched bell or shelf at a low corner or a wide Q, and the\nfitted low-pass's b2 far below Nyquist); they are held to 1e-13 absolute, and the arm prints the\nlargest gap of each kind. Regenerate the fixture when openmixer's TypeScript changes, until it is\ndeleted. The \u003Ccode>eq-curve\u003C\u002Fcode> arm holds the same functions to closed forms.\u003C\u002Fp>\n\u003Ch2 id=\"loudness\">Loudness\u003C\u002Fh2>\n\u003Cp>\u003Ccode>omx_loudness.h\u003C\u002Fcode> measures EBU R 128 loudness the way ITU-R BS.1770-4 defines it\n(FreeMixer\u002Fomx-dsp#64). The engine measures and accumulates; it reads the figures and publishes\nthem, and no other code computes them.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>K-weighting.\u003C\u002Fstrong> Two sections designed for the rate at init, in double, from the analogue\nprototype of BS.1770's 48 kHz table (a high shelf at 1681.97 Hz, +4.0 dB, and the RLB high-pass\nat 38.135 Hz), by the bilinear transform with \u003Ccode>tan\u003C\u002Fcode> prewarping. At 48 kHz they reproduce the\nstandard's printed coefficients; at the other rates they are the same filter. Each leg runs\nthrough omx_biquad_d(), double history.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Blocks.\u003C\u002Fstrong> The K-weighted squares, each leg weighted (1.0 for L, R, C; 1.41 for the surrounds;\n0 drops a leg, the LFE), are summed in 10 ms sub-blocks (\u003Ccode>rate \u002F 100\u003C\u002Fcode> samples, a whole number at\neach of the nine oracle rates). Momentary loudness is the last 40 sub-blocks (400 ms), short-term\nthe last 300 (3 s); a window that has not yet filled counts the missing time as silence.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Gating.\u003C\u002Fstrong> Every 100 ms a full 400 ms block joins the integrated measurement and, once 3 s have\npassed, the short-term value joins the range. Neither keeps the blocks: each keeps a histogram of\n0.1 LU bins from −70 LUFS up, every bin holding its count and the exact sum of its energies. The\nabsolute gate (−70 LUFS) is a bin edge; the relative gate (−10 LU for the integrated loudness,\n−20 LU for the range) is placed in energy, and the bin it falls in is kept when its own mean is\nabove the gate. So a programme of any length takes the same state, and the integrated value is\nexact except for blocks within 0.1 LU of the relative gate.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Loudness range.\u003C\u002Fstrong> EBU Tech 3342: the 10th to the 95th percentile of the gated short-term\nvalues, each percentile read as the energy mean of the bin that holds it.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>True peak.\u003C\u002Fstrong> The largest reading of omx_truepeak_block() over every leg: the shared ×4\nhalf-band interpolator, BS.1770-4's four, not a second one.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Nothing on the process path allocates, locks or calls libm: the bin edges and coefficients are\ncomputed at init, and a reading calls \u003Ccode>pow\u003C\u002Fcode> and \u003Ccode>log10\u003C\u002Fcode> at most twice. The state is one caller-owned struct\nwithout pointers, exported with OMXDSP_STATE_LAYOUT.\u003C\u002Fp>\n\u003Cp>The oracle (\u003Ccode>test\u002Ffx\u002Floudness.test.c\u003C\u002Fcode>) generates EBU Tech 3341's and 3342's test signals from\ntheir closed forms at each of the nine oracle rates and holds the readings to the tolerances the\ndocuments give: momentary, short-term and integrated ±0.1 LU, true peak +0.2\u002F−0.4 dB, loudness\nrange ±1 LU. Generated: 3341 signals 1 to 6, 9, 12 and 15 to 19, and 3342 signals 1 to 4. In\nplace of the burst signals 10 and 13, a burst of exactly one window (3 s, 400 ms) at −23 dBFS is\nplaced off the 10 ms grid and the largest reading must be −23.0 LUFS ±0.1. Not generated: the\nprogramme recordings (3341 signals 7 and 8, 3342 signals 5 and 6) and the signals published only\nas files (3341 signals 11, 14 and 20 to 23). The true-peak tones are faded in over 20 ms: a sine\nswitched on at full level is a step, and its reconstruction overshoots. The golden digests\n(\u003Ccode>test\u002Fgolden\u002Floudness.sha256\u003C\u002Fcode>) hold the readings in energy, so no libm \u003Ccode>log10\u003C\u002Fcode> enters them;\n\u003Ccode>test\u002Ffx\u002Floudness-perturb.sh\u003C\u002Fcode> breaks the shelf, the relative gate, the range percentile, the true\npeak and the momentary window one at a time, and each must turn the oracle red and move every\ndigest.\u003C\u002Fp>\n\u003Ch2 id=\"the-definitions-and-the-limits\">The definitions and the limits\u003C\u002Fh2>\n\u003Cp>The kernels and DSP constants this library implements are defined in \u003Ccode>data\u002F\u003C\u002Fcode>: one file per kernel\nunder \u003Ccode>data\u002Fkernels\u002F\u003C\u002Fcode>, one per constant under \u003Ccode>data\u002Fprimitives\u002F\u003C\u002Fcode>, in omx-contract's schema. The C\ncode reads them through \u003Ccode>&lt;omxdsp\u002Fomxdsp_definitions.h&gt;\u003C\u002Fcode>, rendered from \u003Ccode>data\u002F\u003C\u002Fcode> by omx-contract's tool\n(\u003Ccode>make definitions OMX_CONTRACT=&lt;checkout&gt;\u003C\u002Fcode>, which runs \u003Ccode>omx-contract render --target c --package omx-dsp\u003C\u002Fcode>) and committed, so a build needs no node. Never edit the header: change the definition and\nrender again; \u003Ccode>make definitions-check\u003C\u002Fcode> fails while the two differ, and \u003Ccode>tools\u002Fcontract-single-source.sh\u003C\u002Fcode>\nfails on a second definition of any of its names. \u003Ccode>make install\u003C\u002Fcode> installs \u003Ccode>data\u002F\u003C\u002Fcode> into omx-contract's\ncontract directory as the package \u003Ccode>omx-dsp\u003C\u002Fcode>, with a \u003Ccode>MANIFEST.json\u003C\u002Fcode>, where the other packages read\nit.\u003C\u002Fp>\n\u003Cp>The facts no single package implements (the declared sample rates, the parameter scales) stay in\nFreeMixer\u002Fomx-contract, read through \u003Ccode>&lt;omxcontract\u002Fomx_contract_limits.h&gt;\u003C\u002Fcode>, which the definitions\nheader includes. That dependency is declared once, in the packaging: \u003Ccode>packaging\u002Fomx-dsp.spec\u003C\u002Fcode>\n(\u003Ccode>BuildRequires\u003C\u002Fcode>, \u003Ccode>Requires\u003C\u002Fcode>) and \u003Ccode>debian\u002Fcontrol\u003C\u002Fcode> (\u003Ccode>Build-Depends\u003C\u002Fcode>, \u003Ccode>Depends\u003C\u002Fcode>) carry the same floor\nand no upper bound (\u003Ccode>&gt;= 3.1.0\u003C\u002Fcode>), and \u003Ccode>omxdsp.pc\u003C\u002Fcode> requires the same floor. Install\nit from the FreeMixer channel with the rest of the build dependencies:\u003C\u002Fp>\n\u003Cpre>\u003Ccode>dnf builddep packaging\u002Fomx-dsp.spec     # Fedora, with the FreeMixer repository added\napt-get build-dep .\u002F                    # Debian, with the FreeMixer source list added\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>tools\u002Fcontract-include.sh\u003C\u002Fcode> finds the installed \u003Ccode>omx-contract\u003C\u002Fcode> with \u003Ccode>pkg-config\u003C\u002Fcode>: at or above the\nfloor the spec declares, it prints the package's include directory; otherwise the build stops, naming\nthe package, the floor and the version installed. The dependency is a floor only, never a ceiling.\nNothing is downloaded and no version is pinned anywhere else. \u003Ccode>tools\u002Fcontract-include-selftest.sh\u003C\u002Fcode>\n(\u003Ccode>make checks\u003C\u002Fcode>) holds the script to that floor, \u003Ccode>debian\u002Fcontrol\u003C\u002Fcode> and \u003Ccode>omxdsp.pc.in\u003C\u002Fcode> to the spec's, and\nrefuses an upper bound in any of them.\u003C\u002Fp>\n\u003Cp>\u003Ccode>OMX_CONTRACT_INC=&lt;dir&gt;\u003C\u002Fcode> names another include directory (one that holds \u003Ccode>omxcontract\u002F\u003C\u002Fcode>) and skips the\nversion check. The perturbation arms use it, and so can you: render a contract with one value moved\ninto a scratch directory, \u003Ccode>make test\u003C\u002Fcode>, and the kernel test that reads that value must go red.\nMoving the range is one commit of its own, in the spec and \u003Ccode>debian\u002Fcontrol\u003C\u002Fcode> together. A value the\nkernels need is added to its definition in \u003Ccode>data\u002F\u003C\u002Fcode> and the header rendered again; it is never typed\nin the code.\u003C\u002Fp>\n\u003Ch2 id=\"packages\">Packages\u003C\u002Fh2>\n\u003Cp>\u003Ccode>packaging\u002Fomx-dsp.spec\u003C\u002Fcode> (RPM, \u003Ccode>omx-dsp-devel\u003C\u002Fcode>) and \u003Ccode>debian\u002F\u003C\u002Fcode> (\u003Ccode>libomxdsp-dev\u003C\u002Fcode>). A \u003Ccode>v&lt;version&gt;\u003C\u002Fcode>\ntag releases both through the FreeMixer release workflows; the version is\n\u003Ccode>OMXDSP_VERSION_*\u003C\u002Fcode> in \u003Ccode>include\u002Fomxdsp\u002Fomxdsp.h\u003C\u002Fcode>, the spec's \u003Ccode>Version:\u003C\u002Fcode> and the top of\n\u003Ccode>debian\u002Fchangelog\u003C\u002Fcode>, and CI refuses them disagreeing.\u003C\u002Fp>\n",[47],{"slug":48,"title":49},"","omx-dsp",1793954995965]